mysql种编译码写在哪_深入理解Java虚拟机(程序编译与代码优化)
對于性能和效率的追求一直是程序開發(fā)中永恒不變的宗旨,除了我們自己在編碼過程中要充分考慮代碼的性能和效率,虛擬機(jī)在編譯階段也會(huì)對代碼進(jìn)行優(yōu)化。本文就從虛擬機(jī)層面來看看虛擬機(jī)對我們所編寫的代碼采用了哪些優(yōu)化手段。
一. 編譯期優(yōu)化
Java 語言的「編譯期」其實(shí)是一段「不確定」的操作過程。因?yàn)樗赡苁且粋€(gè)前端編譯器(如 Javac)把 *.java 文件編譯成 *.class 文件的過程;也可能是程序運(yùn)行期的即時(shí)編譯器(JIT 編譯器,Just In Time Compiler)把字節(jié)碼文件編譯成機(jī)器碼的過程;還可能是靜態(tài)提前編譯器(AOT 編譯器,Ahead Of Time Compiler)直接把 *.java 文件編譯成本地機(jī)器碼的過程。
Javac 這類編譯器對代碼的運(yùn)行效率幾乎沒有任何優(yōu)化措施,虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把對性能的優(yōu)化都放到了后端的即時(shí)編譯器中,這樣可以讓那些不是由 Javac 產(chǎn)生的 class 文件(如 Groovy、Kotlin 等語言產(chǎn)生的 class 文件)也能享受到編譯器優(yōu)化帶來的好處。但是 Javac 做了很多針對 Java 語言編碼過程的優(yōu)化措施來改善
程序員的編碼風(fēng)格、提升編碼效率。相當(dāng)多新生的 Java 語法特性,都是靠編譯器的「語法糖」來實(shí)現(xiàn)的,而不是依賴虛擬機(jī)的底層改進(jìn)來支持。
Java 中即時(shí)編譯器在運(yùn)行期的優(yōu)化過程對于程序運(yùn)行來說更重要,而前端編譯器在編譯期的優(yōu)化過程對于程序編碼來說更加密切。
1Javac 編譯器
Javac 編譯器的編譯過程大致可分為 3 個(gè)步驟:
解析與填充符號表;
插入式注解處理器的注解處理;
分析與字節(jié)碼生成。
這 3 個(gè)步驟之間的關(guān)系如下圖所示:
解析與填充符號表
解析步驟包含了經(jīng)典程序編譯原理中的詞法分析和語法分析兩個(gè)過程;完成詞法分析和語法分析之后,下一步就是填充符號表的過程。符號表是由一組符號地址和符號信息構(gòu)成的表格。在語義分析中,符號表所登記的內(nèi)容將用于語義檢查和產(chǎn)生中間代碼。在目標(biāo)代碼生成階段,當(dāng)對符號名進(jìn)行地址分配時(shí),符號表是地址分配的依據(jù)。
注解處理器
注解(Annotation)是在 JDK 1.5 中新增的,有了編譯器注解處理的標(biāo)準(zhǔn) API 后,我們的代碼就可以干涉編譯器的行為,比如在編譯期生成 class 文件。
語義分析與字節(jié)碼生成
語法分析之后,編譯器獲得了程序代碼的抽象語法樹表示,語法樹能表示一個(gè)結(jié)構(gòu)正確的源程序的抽象,但無法保證源程序是符合邏輯的。而語義分析的主要任務(wù)是對結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查,比如進(jìn)行類型審查。
字節(jié)碼生成是 Javac 編譯過程的最后一個(gè)階段,字節(jié)碼生成階段不僅僅是把前面各個(gè)步驟所生成的信息(語法樹、符號表)轉(zhuǎn)化成字節(jié)碼寫到磁盤中,編譯器還進(jìn)行了少量的代碼添加和轉(zhuǎn)換工作。如前面提到的() 方法就是在這一階段添加到語法樹中的。
在字節(jié)碼生成階段,除了生成構(gòu)造器以外,還有一些其它的代碼替換工作用于優(yōu)化程序的實(shí)現(xiàn)邏輯,如把字符串的加操作替換為 StringBiulder 或 StringBuffer。
完成了對語法樹的遍歷和調(diào)整之后,就會(huì)把填充了所需信息的符號表交給 com.sun.tools.javac.jvm.ClassWriter 類,由這個(gè)類的 writeClass() 方法輸出字節(jié)碼,最終生成字節(jié)碼文件,到此為止整個(gè)編譯過程就結(jié)束了。
2Java 語法糖
Java 中提供了有很多語法糖來方便程序開發(fā),雖然語法糖不會(huì)提供實(shí)質(zhì)性的功能改進(jìn),但是它能提升開發(fā)效率、語法的嚴(yán)謹(jǐn)性、減少編碼出錯(cuò)的機(jī)會(huì)。下面我們來了解下語法糖背后我們看不見的東西。
泛型與類型擦除
泛型顧名思義就是類型泛化,本質(zhì)是參數(shù)化類型的應(yīng)用,也就是說操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)。這種參數(shù)可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口和泛型方法。
在 Java 語言還沒有泛型的時(shí)候,只能通過 Object 是所有類型的父類和強(qiáng)制類型轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來實(shí)現(xiàn)類型泛化。例如 HashMap 的 get() 方法返回的就是一個(gè) Object 對象,那么只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè) Object 到底是個(gè)什么類型的對象。在編譯期間,編譯器無法檢查這個(gè) Object 的強(qiáng)制類型轉(zhuǎn)換是否成功,如果僅僅依賴程序員去保障這項(xiàng)操作的正確性,許多 ClassCastException 的風(fēng)險(xiǎn)就會(huì)轉(zhuǎn)嫁到程序運(yùn)行期。
Java 語言中泛型只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來的原生類型,并且在相應(yīng)的地方插入了強(qiáng)制類型轉(zhuǎn)換的代碼。因此對于運(yùn)行期的 Java 語言來說, ArrayList與 ArrayList是同一個(gè)類型,所以泛型實(shí)際上是 Java 語言的一個(gè)語法糖,這種泛型的實(shí)現(xiàn)方法稱為類型擦除。
自動(dòng)裝箱、拆箱與遍歷循環(huán)
自動(dòng)裝箱、拆箱與遍歷循環(huán)是 Java 語言中用得最多的語法糖。這塊比較簡單,我們直接看代碼:
public?class?SyntaxSugars?{????public?static?void?main(String[]?args){????????List?list?=?Arrays.asList(1,2,3,4,5);????????int?sum?=?0;????????for(int?i?:?list){????????????sum?+=?i;????????}????????System.out.println("sum?=?"?+?sum);????}}
自動(dòng)裝箱、拆箱與遍歷循環(huán)編譯之后:
public?class?SyntaxSugars?{????public?static?void?main(String[]?args)?{????????List?list?=?Arrays.asList(new?Integer[]{????????????????Integer.valueOf(1),????????????????Integer.valueOf(2),????????????????Integer.valueOf(3),????????????????Integer.valueOf(4),????????????????Integer.valueOf(5)????????});????????int?sum?=?0;????????for?(Iterator?iterable?=?list.iterator();?iterable.hasNext();?)?{????????????int?i?=?((Integer)?iterable.next()).intValue();????????????sum?+=?i;????????}????????System.out.println("sum?=?"?+?sum);????}}
第一段代碼包含了泛型、自動(dòng)裝箱、自動(dòng)拆箱、遍歷循環(huán)和變長參數(shù) 5 種語法糖,第二段代碼則展示了它們在編譯后的變化。
條件編譯
Java 語言中條件編譯的實(shí)現(xiàn)也是一顆語法糖,根據(jù)布爾常量值的真假,編譯器會(huì)把分支中不成立的代碼塊消除。
public?static?void?main(String[]?args)?{????if?(true)?{????????System.out.println("block?1");????}?else?{????????System.out.println("block?2");????}}
上述代碼經(jīng)過編譯后 class 文件的反編譯結(jié)果:
public?static?void?main(String[]?args)?{????System.out.println("block?1");}
二.?運(yùn)行期優(yōu)化
在部分商業(yè)虛擬機(jī)中,Java 最初是通過解釋器解釋執(zhí)行的,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或者代碼塊的運(yùn)行特別頻繁時(shí),就會(huì)把這些代碼認(rèn)定為「熱點(diǎn)代碼」(Hot Spot Code)。為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時(shí),虛擬機(jī)將會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼,并進(jìn)行各種層次的優(yōu)化,完成這個(gè)任務(wù)的編譯器稱為即時(shí)編譯器(JIT)。
即時(shí)編譯器不是虛擬機(jī)必須的部分,Java 虛擬機(jī)規(guī)范并沒有規(guī)定虛擬機(jī)內(nèi)部必須要有即時(shí)編譯器存在,更沒有限定或指導(dǎo)即時(shí)編譯器應(yīng)該如何實(shí)現(xiàn)。但是 JIT 編譯性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機(jī)優(yōu)秀與否的最關(guān)鍵指標(biāo)之一。
1HotSpot?虛擬機(jī)內(nèi)的即時(shí)編譯器
由于 Java 虛擬機(jī)規(guī)范中沒有限定即時(shí)編譯器如何實(shí)現(xiàn),所以本節(jié)的內(nèi)容完全取決于虛擬機(jī)的具體實(shí)現(xiàn)。我們這里拿 HotSpot 來說明,不過后面的內(nèi)容涉及具體實(shí)現(xiàn)細(xì)節(jié)的內(nèi)容很少,主流虛擬機(jī)中 JIT 的實(shí)現(xiàn)又有頗多相似之處,因此對理解其它虛擬機(jī)的實(shí)現(xiàn)也有很高的參考價(jià)值。
解釋器與編譯器
盡管并不是所有的 Java 虛擬機(jī)都采用解釋器與編譯器并存的架構(gòu),但許多主流的商用虛擬機(jī),如 HotSpot、J9 等,都同時(shí)包含解釋器與編譯器。
解釋器與編譯器兩者各有優(yōu)勢:
當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用,省去編譯的時(shí)間,立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地機(jī)器碼之后,可以獲得更高的執(zhí)行效率。
當(dāng)程序運(yùn)行環(huán)境中內(nèi)存資源限制較大(如部分嵌入式系統(tǒng)),可以使用解釋器執(zhí)行來節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來提升效率。
同時(shí),解釋器還可以作為編譯器激進(jìn)優(yōu)化時(shí)的一個(gè)「逃生門」,當(dāng)編譯器根據(jù)概率選擇一些大多數(shù)時(shí)候都能提升運(yùn)行速度的優(yōu)化手段,當(dāng)激進(jìn)優(yōu)化的假設(shè)不成立,如加載了新的類后類型繼承結(jié)構(gòu)出現(xiàn)變化、出現(xiàn)「罕見陷阱」時(shí)可以通過逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行。
編譯對象與觸發(fā)條件
程序在運(yùn)行過程中會(huì)被即時(shí)編譯器編譯的「熱點(diǎn)代碼」有兩類:
被多次調(diào)用的方法;
被多次執(zhí)行的循環(huán)體。
這兩種被多次重復(fù)執(zhí)行的代碼,稱之為「熱點(diǎn)代碼」。
對于被多次調(diào)用的方法,方法體內(nèi)的代碼自然會(huì)被執(zhí)行多次,理所當(dāng)然的就是熱點(diǎn)代碼。
而對于多次執(zhí)行的循環(huán)體則是為了解決一個(gè)方法只被調(diào)用一次或者少量幾次,但是方法體內(nèi)部存在循環(huán)次數(shù)較多的循環(huán)體問題,這樣循環(huán)體的代碼也被重復(fù)執(zhí)行多次,因此這些代碼也是熱點(diǎn)代碼。
對于第一種情況,由于是方法調(diào)用觸發(fā)的編譯,因此編譯器理所當(dāng)然地會(huì)以整個(gè)方法作為編譯對象,這種編譯也是虛擬機(jī)中標(biāo)準(zhǔn)的 JIT 編譯方式。而對于后一種情況,盡管編譯動(dòng)作是由循環(huán)體所觸發(fā)的,但是編譯器依然會(huì)以整個(gè)方法(而不是單獨(dú)的循環(huán)體)作為編譯對象。這種編譯方式因?yàn)榘l(fā)生在方法執(zhí)行過程中,因此形象地稱之為棧上替換(On Stack Replacement,簡稱 OSR 編譯,即方法棧幀還在棧上,方法就被替換了)。
我們反復(fù)提到多次,可是多少次算多次呢?虛擬機(jī)如何統(tǒng)計(jì)一個(gè)方法或一段代碼被執(zhí)行過多少次呢?回答了這兩個(gè)問題,也就回答了即時(shí)編譯器的觸發(fā)條件。
判斷一段代碼是不是熱點(diǎn)代碼,是不是需要觸發(fā)即時(shí)編譯,這樣的行為稱為「熱點(diǎn)探測」。其實(shí)進(jìn)行熱點(diǎn)探測并不一定需要知道方法具體被調(diào)用了多少次,目前主要的熱點(diǎn)探測判定方式有兩種。
基于采樣的熱點(diǎn)探測:采用這種方法的虛擬機(jī)會(huì)周期性地檢查各個(gè)線程棧頂,如果發(fā)現(xiàn)某個(gè)(或某些)方法經(jīng)常出現(xiàn)在棧頂,那這個(gè)方法就是「熱點(diǎn)方法」。基于采樣的熱點(diǎn)探測的好處是實(shí)現(xiàn)簡單、高效,還可以很容易地獲取方法調(diào)用關(guān)系(將調(diào)用棧展開即可),缺點(diǎn)是很難精確地確認(rèn)一個(gè)方法的熱度,容易因?yàn)槭艿骄€程阻塞或別的外界因數(shù)的影響而擾亂熱點(diǎn)探測。
基于計(jì)數(shù)器的熱點(diǎn)探測:采用這種方法的虛擬機(jī)會(huì)為每個(gè)方法(甚至代碼塊)建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過一定的閾值就認(rèn)為它是「熱點(diǎn)方法」。這種統(tǒng)計(jì)方法實(shí)現(xiàn)起來麻煩一些,需要為每個(gè)方法建立并維護(hù)計(jì)數(shù)器,而且不能直接獲取到方法的調(diào)用關(guān)系,但是統(tǒng)計(jì)結(jié)果相對來說更加精確和嚴(yán)謹(jǐn)。
HotSpot 虛擬機(jī)采用的是第二種:基于計(jì)數(shù)器的熱點(diǎn)探測。因此它為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Invocation Counter)和回邊計(jì)數(shù)器(Back Edge Counter)。
在確定虛擬機(jī)運(yùn)行參數(shù)的情況下,這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值,當(dāng)計(jì)數(shù)器超過閾值就會(huì)觸發(fā) JIT 編譯。
方法調(diào)用計(jì)數(shù)器
顧名思義,這個(gè)計(jì)數(shù)器用于統(tǒng)計(jì)方法被調(diào)用的次數(shù)。當(dāng)一個(gè)方法被調(diào)用時(shí),會(huì)首先檢查該方法是否存在被 JIT 編譯過的版本,如果存在,則優(yōu)先使用編譯后的本地代碼來執(zhí)行。如果不存在,則將此方法的調(diào)用計(jì)數(shù)器加 1,然后判斷方法調(diào)用計(jì)數(shù)器與回邊計(jì)數(shù)器之和是否超過方法調(diào)用計(jì)數(shù)器的閾值。如果超過閾值,將會(huì)向即時(shí)編譯器提交一個(gè)該方法的代碼編譯請求。
如果不做任何設(shè)置,執(zhí)行引擎不會(huì)同步等待編譯請求完成,而是繼續(xù)進(jìn)入解釋器按照解釋方式執(zhí)行字節(jié)碼,直到提交的請求被編譯器編譯完成。當(dāng)編譯完成后,這個(gè)方法的調(diào)用入口地址就會(huì)被系統(tǒng)自動(dòng)改寫成新的,下一次調(diào)用該方法時(shí)就會(huì)使用已編譯的版本。
如果不做任何設(shè)置,方法調(diào)用計(jì)數(shù)器統(tǒng)計(jì)的并不是方法被調(diào)用的絕對次數(shù),而是一個(gè)相對的執(zhí)行頻率,即一段時(shí)間內(nèi)方法調(diào)用的次數(shù)。當(dāng)超過一定的時(shí)間限度,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時(shí)編譯器編譯,那這個(gè)方法的調(diào)用計(jì)數(shù)器值就會(huì)被減少一半,這個(gè)過程稱為方法調(diào)用計(jì)數(shù)器熱度的衰減,而這段時(shí)間就稱為此方法統(tǒng)計(jì)的半衰期。
進(jìn)行熱度衰減的動(dòng)作是在虛擬機(jī)進(jìn)行 GC 時(shí)順便進(jìn)行的,可以設(shè)置虛擬機(jī)參數(shù)來關(guān)閉熱度衰減,讓方法計(jì)數(shù)器統(tǒng)計(jì)方法調(diào)用的絕對次數(shù),這樣,只要系統(tǒng)運(yùn)行時(shí)間足夠長,絕大部分方法都會(huì)被編譯成本地代碼。此外還可以設(shè)置虛擬機(jī)參數(shù)調(diào)整半衰期的時(shí)間。
回邊計(jì)數(shù)器
回邊計(jì)數(shù)器的作用是統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為「回邊」(Back Edge)。建立回邊計(jì)數(shù)器統(tǒng)計(jì)的目的是為了觸發(fā) OSR 編譯。
當(dāng)解釋器遇到一條回邊指令時(shí),會(huì)先查找將要執(zhí)行的代碼片段是否已經(jīng)有編譯好的版本,如果有,它將優(yōu)先執(zhí)行已編譯的代碼,否則就把回邊計(jì)數(shù)器值加 1,然后判斷方法調(diào)用計(jì)數(shù)器和回邊計(jì)數(shù)器值之和是否超過計(jì)數(shù)器的閾值。當(dāng)超過閾值時(shí),將會(huì)提交一個(gè) OSR 編譯請求,并且把回邊計(jì)數(shù)器的值降低一些,以便繼續(xù)在解釋器中執(zhí)行循環(huán),等待編譯器輸出編譯結(jié)果。
與方法計(jì)數(shù)器不同,回邊計(jì)數(shù)器沒有計(jì)算熱度衰減的過程,因此這個(gè)計(jì)數(shù)器統(tǒng)計(jì)的就是該方法循環(huán)執(zhí)行的絕對次數(shù)。當(dāng)計(jì)數(shù)器溢出時(shí),它還會(huì)把方法計(jì)數(shù)器的值也調(diào)整到溢出狀態(tài),這樣下次再進(jìn)入該方法的時(shí)候就會(huì)執(zhí)行標(biāo)準(zhǔn)編譯過程。
2編譯優(yōu)化技術(shù)
我們都知道,以編譯方式執(zhí)行本地代碼比解釋執(zhí)行方式更快,一方面是因?yàn)楣?jié)約了虛擬機(jī)解釋執(zhí)行字節(jié)碼額外消耗的時(shí)間;另一方面是因?yàn)樘摂M機(jī)設(shè)計(jì)團(tuán)隊(duì)幾乎把所有對代碼的優(yōu)化措施都集中到了即時(shí)編譯器中。這一小節(jié)我們來介紹下 HotSpot 虛擬機(jī)的即時(shí)編譯器在編譯代碼時(shí)采用的優(yōu)化技術(shù)。
優(yōu)化技術(shù)概覽
代碼優(yōu)化技術(shù)有很多,實(shí)現(xiàn)這些優(yōu)化也很有難度,但是大部分還是比較好理解的。為了便于介紹,我們先從一段簡單的代碼開始,看看虛擬機(jī)會(huì)做哪些代碼優(yōu)化。
static?class?B?{????int?value;????final?int?get()?{????????return?value;????}}public?void?foo()?{????y?=?b.get();????z?=?b.get();????sum?=?y?+?z;}
首先需要明確的是,這些代碼優(yōu)化是建立在代碼的某種中間表示或者機(jī)器碼上的,絕不是建立在 Java 源碼上。這里之所使用 Java 代碼來介紹是為了方便演示。
上面這段代碼看起來簡單,但是有許多可以優(yōu)化的地方。
第一步是進(jìn)行方法內(nèi)聯(lián)(Method Inlining),方法內(nèi)聯(lián)的重要性要高于其它優(yōu)化措施。方法內(nèi)聯(lián)的目的主要有兩個(gè),一是去除方法調(diào)用的成本(比如建立棧幀),二是為其它優(yōu)化建立良好的基礎(chǔ),方法內(nèi)聯(lián)膨脹之后可以便于更大范圍上采取后續(xù)的優(yōu)化手段,從而獲得更好的優(yōu)化效果。因此,各種編譯器一般都會(huì)把內(nèi)聯(lián)優(yōu)化放在優(yōu)化序列的最前面。內(nèi)聯(lián)優(yōu)化后的代碼如下:
public?void?foo()?{????y?=?b.value;????z?=?b.value;????sum?=?y?+?z;}
第二步進(jìn)行冗余消除,代碼中「z = b.value;」可以被替換成「z = y」。這樣就不用再去訪問對象 b 的局部變量。如果把 b.value 看做是一個(gè)表達(dá)式,那也可以把這項(xiàng)優(yōu)化工作看成是公共子表達(dá)式消除。優(yōu)化后的代碼如下:
public?void?foo()?{????y?=?b.value;????z?=?y;????sum?=?y?+?z;}
第三步進(jìn)行復(fù)寫傳播,因?yàn)檫@段代碼里沒有必要使用一個(gè)額外的變量 z,它與變量 y 是完全等價(jià)的,因此可以使用 y 來代替 z。復(fù)寫傳播后的代碼如下:
public?void?foo()?{????y?=?b.value;????y?=?y;????sum?=?y?+?y;}
第四步進(jìn)行無用代碼消除。無用代碼可能是永遠(yuǎn)不會(huì)執(zhí)行的代碼,也可能是完全沒有意義的代碼。因此,又被形象的成為「Dead Code」。上述代碼中 y = y 是沒有意義的,因此進(jìn)行無用代碼消除后的代碼是這樣的:
public?void?foo()?{????y?=?b.value;????sum?=?y?+?y;}
經(jīng)過這四次優(yōu)化后,最新優(yōu)化后的代碼和優(yōu)化前的代碼所達(dá)到的效果是一致的,但是優(yōu)化后的代碼執(zhí)行效率會(huì)更高。編譯器的這些優(yōu)化技術(shù)實(shí)現(xiàn)起來是很復(fù)雜的,但是想要理解它們還是很容易的。接下來我們再講講如下幾項(xiàng)最有代表性的優(yōu)化技術(shù)是如何運(yùn)作的,它們分別是:
公共子表達(dá)式消除;
數(shù)組邊界檢查消除;
方法內(nèi)聯(lián);
逃逸分析。
公共子表達(dá)式消除
如果一個(gè)表達(dá)式 E 已經(jīng)計(jì)算過了,并且從先前的計(jì)算到現(xiàn)在 E 中所有變量的值都沒有發(fā)生變化,那么 E 的這次出現(xiàn)就成了公共子表達(dá)式。對于這種表達(dá)式,沒有必要花時(shí)間再對它進(jìn)行計(jì)算,只需要直接使用前面計(jì)算過的表達(dá)式結(jié)果代替 E 就好了。如果這種優(yōu)化僅限于程序的基本塊內(nèi),便稱為局部公共子表達(dá)式消除,如果這種優(yōu)化的范圍覆蓋了多個(gè)基本塊,那就稱為全局公共子表達(dá)式消除。
數(shù)組邊界檢查消除
如果有一個(gè)數(shù)組 array[],在 Java 中訪問數(shù)組元素 array[i] 的時(shí)候,系統(tǒng)會(huì)自動(dòng)進(jìn)行上下界的范圍檢查,即檢查 i 必須滿足 i >= 0 && i < array.length,否則會(huì)拋出一個(gè)運(yùn)行時(shí)異常:java.lang.ArrayIndexOutOfBoundsException,這就是數(shù)組邊界檢查。
對于虛擬機(jī)執(zhí)行子系統(tǒng)來說,每次數(shù)組元素的讀寫都帶有一次隱含的條件判定操作,對于擁有大量數(shù)組訪問的程序代碼,這是一種不小的性能開銷。為了安全,數(shù)組邊界檢查是必須做的,但是數(shù)組邊界檢查并不一定每次都要進(jìn)行。比如在循環(huán)的時(shí)候訪問數(shù)組,如果編譯器只要通過數(shù)據(jù)流分析就知道循環(huán)變量是不是在區(qū)間 [0, array.length] 之內(nèi),那在整個(gè)循環(huán)中就可以把數(shù)組的上下界檢查消除。
方法內(nèi)聯(lián)
方法內(nèi)聯(lián)前面已經(jīng)通過代碼分析介紹過,這里就不再贅述了。
逃逸分析
逃逸分析不是直接優(yōu)化代碼的手段,而是為其它優(yōu)化手段提供依據(jù)的分析技術(shù)。逃逸分析的基本行為就是分析對象的動(dòng)態(tài)作用域:當(dāng)一個(gè)對象在方法中被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其它方法中,稱為方法逃逸。甚至還有可能被外部線程訪問到,例如賦值給類變量或可以在其他線程中訪問的實(shí)例變量,稱為線程逃逸。
如果能證明一個(gè)對象不會(huì)逃逸到方法或者線程之外,也就是別的方法和線程無法通過任何途徑訪問到這個(gè)方法,則可能為這個(gè)變量進(jìn)行一些高效優(yōu)化。比如:
棧上分配:如果確定一個(gè)對象不會(huì)逃逸到方法之外,那么就可以在棧上分配內(nèi)存,對象所占的內(nèi)存空間就可以隨棧幀出棧而銷毀。通常,不會(huì)逃逸的局部對象所占的比例很大,如果能棧上分配就會(huì)大大減輕 GC 的壓力。
同步消除:如果逃逸分析能確定一個(gè)變量不會(huì)逃逸出線程,無法被其它線程訪問,那這個(gè)變量的讀寫就不會(huì)有多線程競爭的問題,因而變量的同步措施也就可以消除了。
標(biāo)量替換:標(biāo)量是指一個(gè)數(shù)據(jù)無法再拆分成更小的數(shù)據(jù)來表示了,Java 虛擬機(jī)中的原始數(shù)據(jù)類型都不能再進(jìn)一步拆分,所以它們就是標(biāo)量。相反,一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它就稱作聚合量,Java 中的對象就是聚合量。如果把一個(gè) Java 對象拆散,根據(jù)訪問情況將其使用到的成員變量恢復(fù)成原始類型來訪問,就叫標(biāo)量替換。如果逃逸分析證明一個(gè)對象不會(huì)被外部訪問,并且這個(gè)對象可以被拆散,那程序執(zhí)行的時(shí)候就可能不創(chuàng)建這個(gè)對象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來替代。對象被拆分后,除了可以讓對象的成員變量在棧上分配和讀寫,還可以為后續(xù)進(jìn)一步的優(yōu)化手段創(chuàng)造條件。
三. 總結(jié)
本文用兩個(gè)小節(jié)分別介紹了 Java 程序從源代碼編譯成字節(jié)碼和從字節(jié)碼編譯成本地機(jī)器碼的過程,Javac 字節(jié)碼編譯器與虛擬機(jī)內(nèi)的 JIT 編譯器的執(zhí)行過程合并起來其實(shí)就等同于一個(gè)傳統(tǒng)編譯器所執(zhí)行的編譯過程。下一篇文章我們來聊聊虛擬機(jī)是如何高效處理并發(fā)的。
參考資料:
《深入理解 Java 虛擬機(jī):JVM 高級特性與最佳實(shí)踐(第 2 版)》
END
往期精彩回顧深入理解 Java 虛擬機(jī)(自動(dòng)內(nèi)存管理機(jī)制)
深入理解 Java 虛擬機(jī)(類文件結(jié)構(gòu))
深入理解 Java 虛擬機(jī)(類加載機(jī)制)
深入理解 Java 虛擬機(jī)(字節(jié)碼執(zhí)行引擎)
BaronTalk
專欄有好文,敬候有緣人長按掃碼關(guān)注我吧?
總結(jié)
以上是生活随笔為你收集整理的mysql种编译码写在哪_深入理解Java虚拟机(程序编译与代码优化)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 空运多少钱啊?
- 下一篇: mysql数据库建表失败_mysql数据