深入理解Java虚拟机——程序编译与代码优化 (转)
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
深入理解Java虛擬機——程序編譯與代碼優(yōu)化 (轉) 博客分類: java一早期(編譯期)優(yōu)化
?
1概述
?
Java語言的“編譯期”是一段“不確定”的操作過程,因為它可能是指一個前端編譯器(其實叫“編譯器的前端”更準確一些)把*.java文件轉變成*.class文件的過程;也可能是指虛擬機的后端運行期編譯器(JIT編譯器,just?in?time?compiler)把字節(jié)碼轉變成機器碼的過程;還可能是指使用靜態(tài)提前編譯器(AOT編譯器,ahead?of?time?compiler)直接把*.java文件編譯成本地機器代碼的過程。下面列舉了這三類編譯過程中一些比較有代表性的編譯器:???????????????????????????????????????????????????????前端編譯器:sun的javac、eclipse?JDT中的增量式編譯器(ECJ)。
?
??JIT編譯器:HotSpot?VM?的C1、C2編譯器。
?
??AOT編譯器:GNU?Compiler?for?the?java、Excelsior?JET。
?
這三類過程中最符合大家對java程序編譯認知的應該是第一類,在后面的講解里,提到的“編譯期”和“編譯器”都僅限于第一類編譯過程。限制了編譯范圍后,對于“優(yōu)化”二字的定義就需要寬松一些,因為javac這類編譯器對代碼的運行效率幾乎沒有任何優(yōu)化措施(在JDK1.3之后,javac的-O優(yōu)化參數就不再有意義了)。虛擬機設計團隊把對性能的優(yōu)化集中到了后端的即時編譯器中,這樣可以讓那些不是由javac產生的class文件也同樣能享受到編譯器優(yōu)化帶來的好處。
?
但是javac做了許多針對編碼過程的優(yōu)化措施來改善程序員的編碼風格和提高編碼效率。相當多新生的java語法特性,都是靠編譯器的“語法糖”來實現,而不是依賴虛擬機的底層改進來支持,可以說,java中即時編譯器在運行期的優(yōu)化過程對于程序運行來說更重要,而前端編譯器在編譯期的優(yōu)化過程對于程序編碼來說關系更加密切。
?
2.javac?編譯器
?
分析源碼是了解一項技術實現內幕的最有效的手段,javac編譯器不像HotSpot虛擬機那樣使用c++語言(包含少量C語言)實現,它本身就是一個由java語言編寫的程序,這為純java的程序員了解它的編譯過程帶來了很大的便利。
?
2.1 javac?的源碼與調試
?
虛擬機規(guī)范嚴格定義了Class文件的格式,但是對如何把java源碼文件轉變?yōu)?/span>class文件的編譯過程未作任何定義,所以這部分內容是與具體JDK實現相關的。從sun?javac的代碼來看,編譯過程大致可以分為三個過程,分別是:
?
- 解析與填充符號表過程;
- ?插入式注解處理器的注解處理過程;
- 分析與字節(jié)碼生成過程。
?
Javac編譯動作的入口是com.sun.tools.javac.main.JavaCompiler類,上述三個過程的代碼邏輯集中在這個類的compile()和compile2()方法里。整個編譯最關鍵的處理是由8個方法來完成的,分別是:
?
[javascript]?view plaincopy
?
1.?initProcessAnnotations(processors);//準備過程:初始化插入式注解處理器??
2.?delegateCompiler?=???
3.?????processAnootations(??//過程2:執(zhí)行注解處理??
4.?????enterTrees(stopIfError(CompileState.PARSE,????//過程1.2:輸入到符號表??
5.?????parseFiles(sourceFileObject))),??????//過程1.1:詞法分析、語法分析??
6.?????classnames);??
7.???????
8.?delegateCompiler.compile2();???//過程3:分析及字節(jié)碼生成??
9.?????case?BY_TODO:??
10. ????while(!?todo.isEmpty())??
11. ????generate(desugar(flow(attribute(todo.remove()))));??
12. ????break;??
13. ????//generate,過程3.4:生成字節(jié)碼??
14. ????//desugar,過程3.3解語法糖??
15. ????//flow,過程3.2:數據流分析??
16. ????//attribute,過程3.1:標注??????
?
語法糖(Syntactic?Sugar),也稱糖衣語法,是由英國計算機科學家Peter?J.?Landin發(fā)明的一個術語,指在計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會。
?
Java在現代編程語言之中屬于“低糖語言”(相對于C#及許多其他jvm語言來說),尤其是JDK1.5之前的版本,“低糖”語法也是java語言被懷疑已經“落后”的一個表面理由。Java中最常用的語法糖主要是泛型、變長參數、自動裝箱拆箱,等等,虛擬機運行時不支持這些語法,它們在編譯階段被還原回簡單的基礎語法結構,這個過程就被稱為解語法糖。
?
3.java語法糖的味道
?
幾 乎各種語言或多或少都提供過一些語法糖來方便程序員的代碼開發(fā),這些語法糖雖然不會提供實質性的功能改進,但是它們或能提高效率,或能提升語法的嚴謹性, 或能減少代碼出錯的機會。不過也有一種觀點認為語法糖并不一定都是有益的,大量添加和使用含糖的語法容易讓程序員產生依賴,無法看清語法糖的糖衣背后程序 代碼的真實面目。
?
總而言之,語法糖可以看做是編譯器實現的一些“小把戲”,這些“小把戲”可能會使得效率有一個“大提升”,但我們也應該去了解這些“小把戲”背后的真實世界,那樣才能利用好它們,而不是被它們所迷惑。
?
3.1泛型與類型擦除
?
泛型是JDK1.5的一項新特性,它的本質是參數化類型(Parameterized?Type)的應用,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口和泛型方法。
?
泛型思想早在C++語言的模板(Template)中就開始生根發(fā)芽,在java語言還沒有出現泛型時,只能通過Object是所有類型的父類和類型強制轉換兩個特點的配合來實現類型泛化。例如在哈希表的存取中,JDK1.5之前使用HashMap的get()方法,返回值就是一個Object對象,由于java語言里面所有的類型都繼承于java.lang.Object,那Object轉型成任何對象都是有可能的。但是也因為有無限的可能,就只有程序員和運行期的虛擬機才知道這個Object到底是個什么類型的對象。在編譯期間,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多ClassCastException的風險就會被轉嫁到程序運行期中。
?
泛型技術在C#和java之中的使用方式看似相同,但實現上卻有著根本性的分歧,C#里面泛型無論在程序源碼之中、編譯后的IL中(Intermediate?Language,中間語言,這時候泛型是一個占位符)還是在運行期的CLR(common?language?runtime)中都是切實存在的,List<int>與List<String>就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現成為類型膨脹,基于這種方法實現的泛型被稱為真實泛型。
?
Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經被替換為原來的原生類型(Raw?Type,也稱為裸類型)了,并且在相應的地方插入了強制轉型代碼,因此對于運行期的java語言來說,ArrayList<int>與ArrayList<String>就是同一個類。所以說泛型技術實際上是java語言的一顆語法糖,java語言中的泛型實現方法稱為類型擦除,基于這種方法實現的泛型被稱為偽泛型。
?
代碼1是一段簡單的java泛型例子,我們看一下它編譯后的結果:
?
1.?<span?style="font-size:18px;"></span><pre?name="code"?class="java">public?static?void?main(String[]?args){??
2.?????????Map<String,?String>?map?=?new?HashMap<String,?String>();??
3.?????????map.put("hello",?"你好");??
4.?????????map.put("how?are?you",?"吃了沒");??
5.?????????System.out.println(map.get("hello"));??
6.?????????System.out.println(map.get("how?are?you"));??
7.?????}??
?
把這段代碼編譯成class文件,然后再用字節(jié)碼反編譯工具進行反編譯后,將會發(fā)現泛型都不見了,程序又變回了java泛型出現之前的寫法,泛型類型都變回了原生類型:
?
1.?<span?style="font-size:16px;">public?static?void?main(String[]?args){??
2.?????????Map?map?=?new?HashMap();??
3.?????????map.put("hello",?"你好");??
4.?????????map.put("how?are?you",?"吃了沒");??
5.?????????System.out.println((String)map.get("hello"));??
6.?????????System.out.println((String)map.get("how?are?you"));??
7.?????????}</span>??
?
當初JDK設計團隊為什么選擇類型擦除的方式來實現java語言的泛型支持呢?是因為實現簡單、兼容性考慮還是別的原因?我們不得而知,但確實有不少人對java語言提供的偽泛型頗有微詞,當時甚至連《thinging?in?java》的作者Bruce?Eckel也發(fā)表了一篇文章《這不是泛型!》來批評JDK1.5的泛型實現。
?
1.?public?class?GenericTypes?{??
2.???
3.?????public?static?void?method(List<String>?list){??
4.?????????System.out.println("invoke?method(List<String>?list)");??
5.?????}??
6.???????
7.?????public?static?void?method(List<Integer>?list){??
8.?????????System.out.println("invoke?method(List<Integer>?list");??
9.?????}??
10. ????}??
?
請想一想,上面這段代碼是否正確,能否編譯執(zhí)行?答案是不能被編譯的,是因為參數List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>,擦除動作導致這兩個方法的特征簽名變的一模一樣。初步來看,無法重載的原因已經找到了,但是真的如此嗎?只能說,泛型擦除成相同的原生類型只是無法重載的一部分原因,請再看一下下面的代碼:
?
1.?public?class?GenericTypes?{??
2.???
3.?????public?static?String?method(List<String>?list){??
4.?????????System.out.println("invoke?method(List<String>?list)");??
5.?return?"";??
6.?????}??
7.???????
8.?????public?static?int?method(List<Integer>?list){??
9.?????????System.out.println("invoke?method(List<Integer>?list");??
10. ????}??
11. ????return?"1";??
12. ????}??
?
這兩段代碼的差別,是兩個method方法添加了不同的返回值,由于這兩個返回值的加入,方法重載居然成功了,即這段代碼可以被編譯和執(zhí)行了。重載的時候,方法名要一樣,但是參數類型和個數不一樣,返回值類型可以相同也可以不相同。無法以返回型別作為重載函數的區(qū)分標準。??
?
上面代碼中的重載當然不是根據返回值來確定的,之所以這次編譯和執(zhí)行能成功,是因為兩個method()方法加入了不同的返回值后才能共存在一個class文件中。之前介紹過class文件內容中方法表(method_info),方法重載要求方法具備不同的特征簽名,返回值并不包含在方法的特征簽名之中,所以返回值不參與重載選擇,但是在class文件格式之中,只要描述符不是完全一致的兩個方法就可以共存。也就是說兩個方法如果有相同的名稱和特征簽名,但返回值不同,那它們也是可以合法的共存于一個class文件中。
?
3.2自動裝箱、拆箱與遍歷循環(huán)
?
自動裝箱、拆箱與遍歷循環(huán)是java語言里使用的最多的語法糖。如下代碼所示:
?
public?static?void?main(String[]?args){??
?
1.?????????List<Integer>?list?=?Arrays.asList(1,2,3,4);??
2.???????????
3.?????????int?sum?=?0;??
4.?????????for(int?i:list){??
5.?????????????sum?+=?i;??
6.?????????}??
7.?????????System.out.println(sum);??
8.?????????}??
9.?????//上述代碼編譯之后的變化:??
10. ????public?static?void?main(String[]?args){??
11. ????????List?list?=?Arrays.asList(new?Integer[]{??
12. ????????????????Integer.valueOf(1),??
13. ????????????????Integer.valueOf(2),??
14. ????????????????Integer.valueOf(3),??
15. ????????????????Integer.valueOf(4),??
16. ????????});??
17. ??????????
18. ????????int?sum?=?0;??
19. ????????for(Iterator?localIterator?=?list.iterator();?localIterator.hasNext();){??
20. ????????????int?i?=?((Integer)localIterator.next()).intValue();??
21. ????????????sum?+=?i;??
22. ????????}??
23. ????????System.out.println(sum);??
24. ????????}??
?
上面第一段代碼中一共包含了泛型、自動裝箱、自動拆箱、遍歷循環(huán)與變長參數五種語法糖,上面第二段代碼則展示了它們在編譯后的變化。泛型就不必說了,自動裝箱、拆箱在編譯之后被轉化成了對應的包裝和還原方法,如本例子中的Integer.valueOf()與Integer.intValue()方法,而遍歷循環(huán)則是把代碼還原成了迭代器的實現,這也是為何遍歷循環(huán)需要被遍歷的類實現Iterable接口的原因。最后再看看變長參數,它在調用的時候變成了一個數組類型的參數,在變長參數出現之前,程序員就是使用數組來完成類似功能的。
?
這些語法糖雖然看起來很簡單,但也不見得就沒有任何值得我們注意的地方,下面的代碼演示了自動裝箱的一些錯誤用法:
?
1.?public?static?void?main(String[]?args){??
2.?????????Integer?a?=?1;??
3.?????????Integer?b?=?2;??
4.?????????Integer?c?=?3;??
5.?????????Integer?d?=?4;??
6.?????????Integer?e?=?321;??
7.?????????Integer?f?=?321;??
8.?????????Long?g?=?3L;??
9.???????????
10. ????????System.out.println(c?==?d);????//false?值不等??
11. ????????System.out.println(e?==?f);????//false??堆位置不同??
12. ????????System.out.println(c?==?(a?+?b));?//true???"+"運算符拆包??
13. ????????System.out.println(c.equals(a?+?b));//true?equals?值比較??
14. ????????System.out.println(g?==?(a+b));//true???"+"運算符拆包??
15. ????????System.out.println(g.equals(a+b));//false???類型不同??
16. ????}??
?
請思考兩個問題:一是代碼中的6句打印語句輸出時什么?二是6句打印語句中,解除語法糖后參數是什么樣?鑒于包裝類的“==”運算在沒有遇到算數運算的情況下不會自動拆箱,而且它們的equals()方法不會處理數據轉型的關系,我們建議在實際編碼中應該盡量避免這樣使用裝箱與拆箱。integer 的范圍:-128~127,在這個范圍內的integer和int是一樣的,還沒有在堆中開辟空間。
?
3.3條件編譯
?
許多程序設計語言都提供了條件編譯的途徑,如C、C++中使用預處理器指示符(#ifdef)來完成條件編譯。C、C++的預處理器最初的任務是解決編譯時的代碼依賴關系(眾所周知的#include預處理指令),而在java語言之中并沒有使用預處理器,因為java語言天然的編譯方式(編譯器并非一個一個的編譯java文件,而是將所有的編譯單元的語法樹頂級節(jié)點輸入到待處理列表后再進行編譯,因此各個文件之間能夠互相提供符號信息)無需使用預處理器。那java語言是否有辦法實現條件編譯呢?
?
Java語言當然也可以進行條件編譯,方法就是使用條件為常量的if語句。如下代碼所示,此代碼中的if語句不同于其他java代碼,它在編譯階段就會被“運行”,生成的字節(jié)碼之中只包括System.out.println("block?1");一條語句,并不會包含if語句及另外一個分支中的System.out.println("block?2");。
?
1.?<span?style="font-family:'Microsoft?YaHei';font-size:16px;">????
2.?????public?static?void?main(String[]?args){??
3.?????????if(true){??
4.?????????????System.out.println("block?1");??
5.?????????}else{??
6.?????????????System.out.println("block?2");??
7.?????????}??
8.?????????}??
9.?????//此代碼編譯后class文件的反編譯結果:??
10. ????public?static?void?main(String[]?args){??
11. ????????????System.out.println("block?1");??
12. ????}</span>??
?
只能使用條件為常量的if語句才能達到上述效果,如果使用常量和其他帶有條件判斷能力的語句搭配,則可能在控制流分析中提示錯誤,被拒絕編譯,如下面代碼所示,就會被編譯器拒絕編譯:
?
1.?<span?style="font-family:'Microsoft?YaHei';font-size:16px;">public?static?void?main(String[]?args){??
2.?????????while(false){??
3.?????????????System.out.print("");??
4.?????????????}</span>??
?
java語言中條件編譯的實現,是java語言的一個語法糖,根據布爾常量值的真假,編譯器將會把分支中不成立的代碼塊消除掉,這一工作將在編譯器解除語法糖的階段完成。由于這種條件編譯的實現方式使用了if語句,所以它必須遵循最基本的java語法,只能寫在方法體內部,因此它只能實現語句基本塊級別的條件編譯,而沒有辦法實現根據條件調整整個java類的結構。
?
除了我們介紹的泛型、自動裝箱、自動拆箱、遍歷循環(huán)、變長參數和條件編譯之外,java語言還有不少其他的語法糖,如內部類、枚舉類、斷言語句、對枚舉和字符串的switch支持、在try語句中定義和關閉資源等,可以通過跟蹤javac源碼、反編譯class文件等方式了解它們的本質實現。
?
?
二.晚期(運行期)優(yōu)化
?
1.概述
?
JAVA最初是通過解釋器進行解釋執(zhí)行的,當虛擬機發(fā)現某個方法或代碼塊的運行特別頻繁,就會把這些代碼認定為“熱點代碼”而將它們編譯成本地機器碼,并進行各種層次的優(yōu)化,完成這個任務的編譯器成為即時編譯器(Just In Time Compiler)。
?
?????? Java虛擬機規(guī)范并沒有規(guī)定虛擬機內必須要有即時編譯器。但是,即時編譯器性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機優(yōu)秀與否的最關鍵的指標之一,它也是虛擬機中最核心最能體現技術水平的部分。
?
2.HotSpot虛擬機內的即使編譯器
?
?????? HOTSPOT和J9都是解釋器和編譯器并存,保留解釋器的原因是,加快啟動時間,立即執(zhí)行,當運行環(huán)境中內存資源限制較大時,解釋器可以節(jié)約內存,解釋器還可以作為激進優(yōu)化的編譯器的“逃生門”(稱為逆優(yōu)化Deoptimization),而編譯器能把越來越多的代碼編譯成本地代碼后,獲取更高的執(zhí)行效率。
?
?????? HOTSPOT內置了兩個即時編譯器,clientcompiler和servercompiler,稱為C1,C2(clientcompiler獲取更高的編譯速度,servercompiler來獲取更好的編譯質量),默認是采用解釋器與其中一個編譯器直接配合的方式工作。HOTSPOT會根據自身版本和宿主機器的性能自動選擇運行模式,用戶也可以使用-client或-server來決。這種解釋器編譯器搭配的方式成為混合模式,用戶還可以使用-Xint強制虛擬機使用“解釋模式”,也可以使用-Xcomp強制“編譯模式”。
?
被編譯的觸發(fā)條件:
?
1.???被多次調用的方法
?
2.???被多次執(zhí)行的循環(huán)體(棧上替換)OSR On StackReplacement
?
判斷是否是熱點代碼的行為成為熱點探測:hot spotdetection,主要的熱點探測方式主要有兩種:
?
1.???基于采樣的熱點探測,JVM會周期性檢查各個線程的棧頂,如果某個方法經常出現在棧頂,那就認定為熱點方法。簡單高效,精度不夠。
?
2.???基于計數器的熱點探測,統計方法執(zhí)行次數。(HOTSPOT使用這種方式)
?
HOTSPOT有兩個計數器:方法調用計數器和回邊計數器
?
方法調用計數器client默認1500次,server默認10000次,可以通過參數-XX:CompileThreshold來設定。調用方法時,會先判斷是否存在編譯過的版本,如果有則調用該版本,否則計數器加1,然后看方法調用計數器和回邊計數器之和是否超過方法調用計數器的閾值。超過,則提交編譯請求
?
方法調用計數器并不是統計方法調用絕對次數,而是一個相對執(zhí)行頻率,超過一定時間,如果方法調用次數不足以讓它提交給編譯器,則計數器就會被減少一半,這種現象稱為熱度衰減(Counter Decay),進行熱度衰減的動作是在垃圾回收時順便進行的,而這段時間就被稱為半衰周期(Counter Half Life Time)可用-XX:-UseCounterDecay來關閉熱度衰減,用-XX:CounterHalfLifeTime來設置半衰時間。
?
回邊計數器用于統計方法中循環(huán)體的執(zhí)行次數。字節(jié)碼遇到控制流向后跳轉 的指令成為回邊。建立回邊計數器統計的目的就是為了觸發(fā)OSR編譯。回邊的控制參數有:
?
-XX:BackEdgeThreshold,-XX:OnStackReplacePercentage。
?
?????? 1.在Client模式下,回邊計數器閥值計算公式:方法調用計數器閥值乘以OSR比率,然后除以100.
?
?????? 2.在server模式下,回邊計數器閥值計算公式:方法調用計數器閥值乘以(OSR比率,然后減去解釋器監(jiān)控比率的差值)除以100。
?
?????? 與方法計數器不同,回邊計數器沒有計數熱度衰減的過程,因此這個計數器統計的就是該方法循環(huán)執(zhí)行的絕對次數。
?
編譯過程
?
?????? 對Client Compiler而言,是一個簡單快速的三段式編譯器,主要關注點在于局部性的優(yōu)化,放棄了許多耗時較長的全局優(yōu)化手段。
?
1.???第一階段,一個平臺獨立的前段將字節(jié)碼構造成一種高級中間代碼表示(HIR)。
?
2.???第二階段,一個平臺相關的后端從HIR中產生低級中間代碼表示(LIR),而在此之前會在HIR上完成一些優(yōu)化。
?
3.???最后節(jié)點是在平臺相關的后端使用線性掃描算法在LIR上分配寄存器,并在LIR上座窺孔優(yōu)化,然后產生極其代碼。
?
?????? 對Server Compiler則是專門面向服務端的典型應用并為服務端的性能配置特別調整過的編譯器。它會執(zhí)行所有的經典的優(yōu)化動作,如:無用代碼消除,循環(huán)展開,循 環(huán)表達式外提,公共子表達式消除,常量傳播,基本塊重排序等,還會實施一些與Java語言特性密切相關的優(yōu)化技術,如范圍檢查消除,空值檢查消除。
?
?
?
3編譯優(yōu)化技術
?
?????? JDK設計團隊幾乎把代碼的所有優(yōu)化措施都集中在了即使編譯器,所以一般來說即即時編譯器產生的本地代碼會比Javac產生的字節(jié)碼更優(yōu)秀。接下來介紹幾種景點優(yōu)化技術:
?
?????? 1.公共子表達式消除:如果一個表達式E已經被計算過了,并且從先前的計算到現在E中所有變量的值都沒有發(fā)生變化,那么E的這次出現就成為了公共子表達 式。若這種優(yōu)化僅限于程序基本塊內,稱為局部公共子表達式消除;若這種優(yōu)化的范圍涵蓋了多個基本塊,就稱為全局公共子表達式消除。
?
?????? 2.數組邊界檢查消除:在Java語言中訪問數組元素的時候系統將會自動進行上下界的范圍檢查,即檢查i必須滿足i>=0 && i<foo.length這個條件。
?
?????? 3.方法內聯:它除了消除方法調用的成本之外,更重要的意義是為其他優(yōu)化手段建立良好的基礎。由于Java語言中默認的實例方法就是虛方法,對于虛方法, 編譯器做內聯的時候根本就無法確定應該使用哪個方法版本。為了解決虛方法的內聯問題,引入了“類型繼承關系分析”。編譯器在進行內聯時,如果是非虛方法, 那么直接進行內聯,如果是虛方法,則會向CHA查詢此方法在當前程序下是否有多個目標版本可供選擇,如果只有一個版本,那也可以進行內聯,不過這種內聯就 屬于激進優(yōu)化,需要預留一個逃生門,萬一加載了導致繼承關系發(fā)生變化的新類,那就需要退回到解釋狀態(tài)執(zhí)行,或者重新編譯。
?
?????? 4.逃逸分析:它是為其他優(yōu)化手段提供依據的分析技術。基本行為就是分析對象動態(tài)作用于,當一個對象在方法里面被頂以后,它可能被外部方法所引用,稱為線 程逃逸。若能證明一個對象不會逃逸到方法或線程之外,就可以進行一些高效的優(yōu)化,如:棧上分配(對象所占用的內存空間可以隨棧幀出棧而銷毀,若在堆里分配 的話,回收和整理內存都需要消耗時間),同步消除(線程同步本身就是一個相對耗時的過程,若確定不會逃逸出線程,對這個變量就不需要實施同步措施),標量 替換(將Java對象拆散,根據程序訪問的情況,將其使用到的成員變量恢復原始類型來訪問,若不會逃逸的話,執(zhí)行程序的時候將可能不創(chuàng)建對象,而直接創(chuàng)建 它的若干個被這個方法使用到的成員變量來代替)。
?
?
?
4Java與c/c++編譯器對比
?
?????? Java與c/c++的編譯器對比實際上代表了最經典的即時編譯器與靜態(tài)編譯器的對比。Java可能會下列原因導致輸出本地代碼有一些劣勢:
?
1.???首先,因為即時編譯器運行占用的是用戶程序的運行時間,具有很大的時間壓力,它能提供的優(yōu)化手段也嚴重受制于編譯成本。
?
2.???其次,Java語言是動態(tài)的類型安全語言,這就意味著需要由虛擬機來確保程序不會違反語言的語義或訪問非結構化內存。
?
3.???第三,Java語言中雖然沒有virtual關鍵字,但是使用虛方法的頻率卻遠遠大于c/c++語言,這就意味著運行時對方法接受者進行多態(tài)選擇的頻率要遠遠大于c/c++語言,也以為即時編譯器在進行一些優(yōu)化時的難度遠遠大于c/c++的靜態(tài)優(yōu)化編譯器。
?
4.???第四,Java語言是可以動態(tài)擴展的語言,運行時加載新的類可能改變程序類型的繼承關系,編譯器不得不時刻注意并隨著類型的變化而在運行時撤銷或重新進行一些優(yōu)化。
?
5.???第五,Java語言中對象的內存分配都是在堆上進行的,只有方法中的局部變量才能在棧上分撇。
Java語言的這些性能上的劣勢都是為了換取開發(fā)效率上的優(yōu)勢,動態(tài)安全、動態(tài)擴展、垃圾回收這些特性都為Java語言的開發(fā)效率做出了很大的貢獻。
轉載于:https://my.oschina.net/xiaominmin/blog/1598027
總結
以上是生活随笔為你收集整理的深入理解Java虚拟机——程序编译与代码优化 (转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring-boot(二)
- 下一篇: java美元兑换,(Java实现) 美元