国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Java編譯器中的優(yōu)化技術(shù)

這篇具有很好參考價值的文章主要介紹了Java編譯器中的優(yōu)化技術(shù)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

一、JIT技術(shù)

?文章來源地址http://www.zghlxwxcb.cn/news/detail-607964.html

????????Java中的熱點代碼主要有兩類,包括:1、被多次調(diào)用的方法。 2、被多次執(zhí)行的循環(huán)體。
前者很好理解,一個方法被調(diào)用得多了,方法體內(nèi)代碼執(zhí)行的次數(shù)自然就多,它成為 熱點代
是理所當(dāng)然的。而后者則是為了解決當(dāng)一個方法只被調(diào)用過一次或少量的幾次,但是方法體內(nèi)部存在循環(huán)次數(shù)較多的循環(huán)體,這樣循環(huán)體的代碼也被重復(fù)執(zhí)行多次,因此這些代碼也應(yīng)該認為是“ 熱點代碼” 。
????????對于這兩種情況,編譯的目標對象都是整個方法體,而不會是單獨的循環(huán)體。第一種情況,由于是依靠方法調(diào)用觸發(fā)的編譯,那編譯器理所當(dāng)然地會以整個方法作為編譯對象,這種編譯也是虛擬機 中標準的即時編譯方式。而對于后一種情況,盡管編譯動作是由循環(huán)體所觸發(fā)的,熱點只是方法的一 部分,但編譯器依然必須以整個方法作為編譯對象,只是執(zhí)行入口(從方法第幾條字節(jié)碼指令開始執(zhí)行)會稍有不同,編譯時會傳入執(zhí)行入口點字節(jié)碼序號(Byte Code Index , BCI )。這種編譯方式因為編譯發(fā)生在方法執(zhí)行的過程中,因此被很形象地稱為“ 棧上替換 On Stack Replacement OSR ),即方法的棧幀還在棧上,方法就被替換了。
????????讀者可能還會有疑問,在上面的描述里,無論是“多次執(zhí)行的方法 ,還是 多次執(zhí)行的代碼塊 , 所謂“ 多次 只定性不定量,并不是一個具體嚴謹?shù)挠谜Z,那到底多少次才算 多次 呢?還有一個問題,就是Java 虛擬機是如何統(tǒng)計某個方法或某段代碼被執(zhí)行過多少次的呢?解決了這兩個問題,也就解答了即時編譯被觸發(fā)的條件。
????????要知道某段代碼是不是熱點代碼,是不是需要觸發(fā)即時編譯,這個行為稱為“ 熱點探測 Hot
Spot Code Detection ),其實進行熱點探測并不一定要知道方法具體被調(diào)用了多少次,目前主流的熱點探測判定方式有兩種 ,分別是:
????????基于采樣的熱點探測(Sample Based Hot Spot Code Detection )。采用這種方法的虛擬機會周期性地檢查各個線程的調(diào)用棧頂,如果發(fā)現(xiàn)某個(或某些)方法經(jīng)常出現(xiàn)在棧頂,那這個方法就是“ 熱點方法” ?;诓蓸拥臒狳c探測的好處是實現(xiàn)簡單高效,還可以很容易地獲取方法調(diào)用關(guān)系(將調(diào)用堆棧展開即可),缺點是很難精確地確認一個方法的熱度,容易因為受到線程阻塞或別的外界因素的影響而擾亂熱點探測。
????????基于計數(shù)器的熱點探測(Counter Based Hot Spot Code Detection )。采用這種方法的虛擬機會為每個方法(甚至是代碼塊)建立計數(shù)器,統(tǒng)計方法的執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過一定的閾值就認為它是“ 熱點方法 。這種統(tǒng)計方法實現(xiàn)起來要麻煩一些,需要為每個方法建立并維護計數(shù)器,而且不能直接獲取到方法的調(diào)用關(guān)系。但是它的統(tǒng)計結(jié)果相對來說更加精確嚴謹。
這兩種探測手段在商用 Java 虛擬機中都有使用到,譬如 J9 用過第一種采樣熱點探測,而在 HotSpot 虛擬機中使用的是第二種基于計數(shù)器的熱點探測方法,為了實現(xiàn)熱點計數(shù), HotSpot 為每個方法準備了兩類計數(shù)器:方法調(diào)用計數(shù)器(Invocation Counter )和回邊計數(shù)器( Back Edge Counter 回邊 的意思就是指在循環(huán)邊界往回跳轉(zhuǎn))。當(dāng)虛擬機運行參數(shù)確定的前提下,這兩個計數(shù)器都有一個明確的閾值,計數(shù)器閾值一旦溢出,就會觸發(fā)即時編譯。
????????我們首先來看看方法調(diào)用計數(shù)器。顧名思義,這個計數(shù)器就是用于統(tǒng)計方法被調(diào)用的次數(shù),它的默認閾值在客戶端模式下是1500 次,在服務(wù)端模式下是 10000 次,這個閾值可以通過虛擬機參數(shù) -XX :CompileThreshold來人為設(shè)定。當(dāng)一個方法被調(diào)用時,虛擬機會先檢查該方法是否存在被即時編譯過的版本,如果存在,則優(yōu)先使用編譯后的本地代碼來執(zhí)行。如果不存在已被編譯過的版本,則將該方法的調(diào)用計數(shù)器值加一,然后判斷方法調(diào)用計數(shù)器與回邊計數(shù)器值之和是否超過方法調(diào)用計數(shù)器的閾值。一旦已超過閾值的話,將會向即時編譯器提交一個該方法的代碼編譯請求。
????????如果沒有做過任何設(shè)置,執(zhí)行引擎默認不會同步等待編譯請求完成,而是繼續(xù)進入解釋器按照解釋方式執(zhí)行字節(jié)碼,直到提交的請求被即時編譯器編譯完成。當(dāng)編譯工作完成后,這個方法的調(diào)用入口地址就會被系統(tǒng)自動改寫成新值,下一次調(diào)用該方法時就會使用已編譯的版本了,整個即時編譯的交互過程如圖11-3 所示。
????????在默認設(shè)置下,方法調(diào)用計數(shù)器統(tǒng)計的并不是方法被調(diào)用的絕對次數(shù),而是一個相對的執(zhí)行頻率,即一段時間之內(nèi)方法被調(diào)用的次數(shù)。當(dāng)超過一定的時間限度,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時編譯器編譯,那該方法的調(diào)用計數(shù)器就會被減少一半,這個過程被稱為方法調(diào)用計數(shù)器熱度的衰減(Counter Decay ),而這段時間就稱為此方法統(tǒng)計的半衰周期( Counter Half Life Time ),進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,可以使用虛擬機參數(shù)-XX - UseCounterDecay來關(guān)閉熱度衰減,讓方法計數(shù)器統(tǒng)計方法調(diào)用的絕對次數(shù),這樣只要系統(tǒng)運行時間足夠長,程序中絕大部分方法都會被編譯成本地代碼。另外還可以使用-XX CounterHalfLifeTime 參數(shù)設(shè)置半衰周期的時間,單位是秒。

Java編譯器中的優(yōu)化技術(shù),JVM,java

????????現(xiàn)在我們再來看看另外一個計數(shù)器—— 回邊計數(shù)器,它的作用是統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行 的次數(shù) ,在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令就稱為 回邊( Back Edge ,很顯然建立回邊計數(shù) 器統(tǒng)計的目的是為了觸發(fā)棧上的替換編譯。
????????關(guān)于回邊計數(shù)器的閾值,雖然HotSpot 虛擬機也提供了一個類似于方法調(diào)用計數(shù)器閾值 -XX
CompileThreshold 的參數(shù) -XX BackEdgeThreshold 供用戶設(shè)置,但是當(dāng)前的 HotSpot 虛擬機實際上并未使用此參數(shù),我們必須設(shè)置另外一個參數(shù)-XX OnStackReplacePercentage 來間接調(diào)整回邊計數(shù)器的閾值,其計算公式有如下兩種。
????????虛擬機運行在客戶端模式下,回邊計數(shù)器閾值計算公式為:方法調(diào)用計數(shù)器閾值(-XX
CompileThreshold )乘以 OSR 比率( -XX OnStackReplacePercentage )除以 100 。其中 -XX
OnStackReplacePercentage 默認值為 933 ,如果都取默認值,那客戶端模式虛擬機的回邊計數(shù)器的閾值為13995。
????????虛擬機運行在服務(wù)端模式下,回邊計數(shù)器閾值的計算公式為:方法調(diào)用計數(shù)器閾值(-XX
CompileThreshold )乘以( OSR 比率( -XX OnStackReplacePercentage )減去解釋器監(jiān)控比率( -XX : InterpreterProfilePercentage)的差值)除以 100 。其中 -XX OnStack ReplacePercentage 默認值為 140 , - XX: InterpreterProfilePercentage 默認值為 33 ,如果都取默認值,那服務(wù)端模式虛擬機回邊計數(shù)器的閾值為10700 。
????????當(dāng)解釋器遇到一條回邊指令時,會先查找將要執(zhí)行的代碼片段是否有已經(jīng)編譯好的版本,如果有的話,它將會優(yōu)先執(zhí)行已編譯的代碼,否則就把回邊計數(shù)器的值加一,然后判斷方法調(diào)用計數(shù)器與回邊計數(shù)器值之和是否超過回邊計數(shù)器的閾值。當(dāng)超過閾值的時候,將會提交一個棧上替換編譯請求,并且把回邊計數(shù)器的值稍微降低一些,以便繼續(xù)在解釋器中執(zhí)行循環(huán),等待編譯器輸出編譯結(jié)果,整個執(zhí)行過程如圖11-4 所示。

Java編譯器中的優(yōu)化技術(shù),JVM,java

二、方法內(nèi)聯(lián)

在前面的講解中,我們多次提到方法內(nèi)聯(lián),說它是編譯器最重要的優(yōu)化手段,甚至都可以不加
之一 。內(nèi)聯(lián)被業(yè)內(nèi)戲稱為優(yōu)化之母,因為除了消除方法調(diào)用的成本之外,它更重要的意義是為其他優(yōu)化手段建立良好的基礎(chǔ),代碼清單11-11 所示的簡單例子就揭示了內(nèi)聯(lián)對其他優(yōu)化手段的巨大價值:沒有內(nèi)聯(lián),多數(shù)其他優(yōu)化都無法有效進行。例子里testInline() 方法的內(nèi)部全部是無用的代碼,但如果不做內(nèi)聯(lián),后續(xù)即使進行了無用代碼消除的優(yōu)化,也無法發(fā)現(xiàn)任何“Dead Code” 的存在。如果分開來看,foo() testInline() 兩個方法里面的操作都有可能是有意義的。代碼清單11-11 未作任何優(yōu)化的字節(jié)碼
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????方法內(nèi)聯(lián)的優(yōu)化行為理解起來是沒有任何困難的,不過就是把目標方法的代碼原封不動地“ 復(fù)
到發(fā)起調(diào)用的方法之中,避免發(fā)生真實的方法調(diào)用而已。但實際上 Java 虛擬機中的內(nèi)聯(lián)過程卻遠沒有想象中容易,甚至如果不是即時編譯器做了一些特殊的努力,按照經(jīng)典編譯原理的優(yōu)化理論,大多數(shù)的Java方法都無法進行內(nèi)聯(lián)。
????????在java中只有使用invokespecial指令調(diào)用的私有方法、實例構(gòu)造器、父類方法和使用invokestatic 指令調(diào)用的靜態(tài)方法才會在編譯期進行解析。除了上述四種方法之外(最多再除去被final 修飾的方法這種特殊情況,盡管它使用invokevirtual 指令調(diào)用,但也是非虛方法,《 Java 語言規(guī)范》中明確說明了這點),其他的 Java 方法調(diào)用都必須在運行時進行方法接收者的多態(tài)選擇,它們都有可能存在多于一個版本的方法接收者,簡而言之,Java 語言中默認的實例方法是虛方法。
????????對于一個虛方法,編譯器靜態(tài)地去做內(nèi)聯(lián)的時候很難確定應(yīng)該使用哪個方法版本,以將代碼清單11-7中所示 b.get() 直接內(nèi)聯(lián)為 b.value 為例,如果不依賴上下文,是無法確定 b 的實際類型是什么的。假如有ParentB SubB 是兩個具有繼承關(guān)系的父子類型,并且子類重寫了父類的 get() 方法,那么 b.get() 是執(zhí)行父類的get() 方法還是子類的 get() 方法,這應(yīng)該是根據(jù)實際類型動態(tài)分派的,而實際類型必須在實際運行到這一行代碼時才能確定,編譯器很難在編譯時得出絕對準確的結(jié)論。
????????更糟糕的情況是,由于Java 提倡使用面向?qū)ο蟮姆绞竭M行編程,而 Java 對象的方法默認就是虛方法,可以說Java 間接鼓勵了程序員使用大量的虛方法來實現(xiàn)程序邏輯。根據(jù)上面的分析可知,內(nèi)聯(lián)與虛方法之間會產(chǎn)生“ 矛盾 ,那是不是為了提高執(zhí)行性能,就應(yīng)該默認給每個方法都使用 final 關(guān)鍵字去修飾呢?C C++ 語言的確是這樣做的,默認的方法是非虛方法,如果需要用到多態(tài),就用 virtual 關(guān)鍵字來修飾,但Java 選擇了在虛擬機中解決這個問題。
????????為了解決虛方法的內(nèi)聯(lián)問題,Java 虛擬機首先引入了一種名為類型繼承關(guān)系分析( Class Hierarchy Analysis, CHA )的技術(shù),這是整個應(yīng)用程序范圍內(nèi)的類型分析技術(shù),用于確定在目前已加載的類中,某個接口是否有多于一種的實現(xiàn)、某個類是否存在子類、某個子類是否覆蓋了父類的某個虛方法等信息。這樣,編譯器在進行內(nèi)聯(lián)時就會分不同情況采取不同的處理:如果是非虛方法,那么直接進行內(nèi)聯(lián)就可以了,這種的內(nèi)聯(lián)是有百分百安全保障的;如果遇到虛方法,則會向CHA 查詢此方法在當(dāng)前程序狀態(tài)下是否真的有多個目標版本可供選擇,如果查詢到只有一個版本,那就可以假設(shè)“ 應(yīng)用程序的全貌就是現(xiàn)在運行的這個樣子” 來進行內(nèi)聯(lián),這種內(nèi)聯(lián)被稱為守護內(nèi)聯(lián)( Guarded Inlining )。不過由于Java 程序是動態(tài)連接的,說不準什么時候就會加載到新的類型從而改變 CHA 結(jié)論,因此這種內(nèi)聯(lián)屬于激進預(yù)測性優(yōu)化,必須預(yù)留好“ 逃生門 ,即當(dāng)假設(shè)條件不成立時的 退路 Slow Path )。假如在程序的后續(xù)執(zhí)行過程中,虛擬機一直沒有加載到會令這個方法的接收者的繼承關(guān)系發(fā)生變化的類,那這個內(nèi)聯(lián)優(yōu)化的代碼就可以一直使用下去。如果加載了導(dǎo)致繼承關(guān)系發(fā)生變化的新類,那么就必須拋棄已經(jīng)編譯的代碼,退回到解釋狀態(tài)進行執(zhí)行,或者重新進行編譯。
????????假如向CHA 查詢出來的結(jié)果是該方法確實有多個版本的目標方法可供選擇,那即時編譯器還將進行最后一次努力,使用內(nèi)聯(lián)緩存(Inline Cache )的方式來縮減方法調(diào)用的開銷。這種狀態(tài)下方法調(diào)用是真正發(fā)生了的,但是比起直接查虛方法表還是要快一些。內(nèi)聯(lián)緩存是一個建立在目標方法正常入口之前的緩存,它的工作原理大致為:在未發(fā)生方法調(diào)用之前,內(nèi)聯(lián)緩存狀態(tài)為空,當(dāng)?shù)谝淮握{(diào)用發(fā)生后,緩存記錄下方法接收者的版本信息,并且每次進行方法調(diào)用時都比較接收者的版本。如果以后進來的每次調(diào)用的方法接收者版本都是一樣的,那么這時它就是一種單態(tài)內(nèi)聯(lián)緩存(Monomorphic Inline Cache)。通過該緩存來調(diào)用,比用不內(nèi)聯(lián)的非虛方法調(diào)用,僅多了一次類型判斷的開銷而已。但如果真的出現(xiàn)方法接收者不一致的情況,就說明程序用到了虛方法的多態(tài)特性,這時候會退化成超多態(tài)內(nèi)聯(lián)緩存(Megamorphic Inline Cache ),其開銷相當(dāng)于真正查找虛方法表來進行方法分派。
??
????????所以說,在多數(shù)情況下Java 虛擬機進行的方法內(nèi)聯(lián)都是一種激進優(yōu)化。事實上,激進優(yōu)化的應(yīng)用在高性能的Java 虛擬機中比比皆是,極為常見。除了方法內(nèi)聯(lián)之外,對于出現(xiàn)概率很?。ㄍㄟ^經(jīng)驗數(shù)據(jù)或解釋器收集到的性能監(jiān)控信息確定概率大?。┑碾[式異常、使用概率很小的分支等都可以被激進優(yōu)化“ 移除 ,如果真的出現(xiàn)了小概率事件,這時才會從 逃生門 回到解釋狀態(tài)重新執(zhí)行。

三、逃逸分析

????????逃逸分析(Escape Analysis )是目前 Java 虛擬機中比較前沿的優(yōu)化技術(shù),它與類型繼承關(guān)系分析一 樣,并不是直接優(yōu)化代碼的手段,而是為其他優(yōu)化措施提供依據(jù)的分析技術(shù)。
????????逃逸分析的基本原理是:分析對象動態(tài)作用域,當(dāng)一個對象在方法里面被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸;甚至還有可能被外部線程訪問到,譬如賦值給可以在其他線程中訪問的實例變量,這種稱為線程逃逸;從不逃逸、方法逃逸到線程逃逸,稱為對象由低到高的不同逃逸程度。
????????
????????如果能證明一個對象不會逃逸到方法或線程之外(換句話說是別的方法或線程無法通過任何途徑訪問到這個對象),或者逃逸程度比較低(只逃逸出方法而不會逃逸出線程),則可能為這個對象實例采取不同程度的優(yōu)化,如:
????????1、棧上分配( Stack Allocations ):在 Java 虛擬機中, Java 堆上分配創(chuàng)建對象的內(nèi)存空間幾乎是 Java程序員都知道的常識, Java 堆中的對象對于各個線程都是共享和可見的,只要持有這個對象的引用,就可以訪問到堆中存儲的對象數(shù)據(jù)。虛擬機的垃圾收集子系統(tǒng)會回收堆中不再使用的對象,但回收動作無論是標記篩選出可回收對象,還是回收和整理內(nèi)存,都需要耗費大量資源。如果確定一個對象不會逃逸出線程之外,那讓這個對象在棧上分配內(nèi)存將會是一個很不錯的主意,對象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀。在一般應(yīng)用中,完全不會逃逸的局部對象和不會逃逸出線程的對象所占的比例是很大的,如果能使用棧上分配,那大量的對象就會隨著方法的結(jié)束而自動銷毀了,垃圾收集子系統(tǒng)的壓力將會下降很多。棧上分配可以支持方法逃逸,但不能支持線程逃逸。
? ? ? ? 2、標量替換(Scalar Replacement ):若一個數(shù)據(jù)已經(jīng)無法再分解成更小的數(shù)據(jù)來表示了, Java 虛擬機中的原始數(shù)據(jù)類型(int long 等數(shù)值類型及 reference 類型等)都不能再進一步分解了,那么這些數(shù)據(jù)就可以被稱為標量。相對的,如果一個數(shù)據(jù)可以繼續(xù)分解,那它就被稱為聚合量(Aggregate ), Java中的對象就是典型的聚合量。如果把一個Java 對象拆散,根據(jù)程序訪問的情況,將其用到的成員變量恢復(fù)為原始類型來訪問,這個過程就稱為標量替換。假如逃逸分析能夠證明一個對象不會被方法外部訪問,并且這個對象可以被拆散,那么程序真正執(zhí)行的時候?qū)⒖赡懿蝗?chuàng)建這個對象,而改為直接創(chuàng)建它的若干個被這個方法使用的成員變量來代替。將對象拆分后,除了可以讓對象的成員變量在棧上(棧上存儲的數(shù)據(jù),很大機會被虛擬機分配至物理機器的高速寄存器中存儲)分配和讀寫之外,還可以為后續(xù)進一步的優(yōu)化手段創(chuàng)建條件。標量替換可以視作棧上分配的一種特例,實現(xiàn)更簡單(不用考慮整個對象完整結(jié)構(gòu)的分配),但對逃逸程度的要求更高,它不允許對象逃逸出方法范圍內(nèi)。
? ? ? ? 3、同步消除(Synchronization Elimination ):線程同步本身是一個相對耗時的過程,如果逃逸分析能夠確定一個變量不會逃逸出線程,無法被其他線程訪問,那么這個變量的讀寫肯定就不會有競爭, 對這個變量實施的同步措施也就可以安全地消除掉。
????????關(guān)于逃逸分析的研究論文早在1999 年就已經(jīng)發(fā)表,但直到 JDK 6 , HotSpot才開始支持初步的逃逸分析,而且到現(xiàn)在這項優(yōu)化技術(shù)尚未足夠成熟,仍有很大的改進余地。不成熟的原因主要是逃逸分析的計算成本非常高,甚至不能保證逃逸分析帶來的性能收益會高于它的消耗。如果要百分之百準確地判斷一個對象是否會逃逸,需要進行一系列復(fù)雜的數(shù)據(jù)流敏感的過程間分析,才能確定程序各個分支執(zhí)行時對此對象的影響。前面介紹即時編譯、提前編譯優(yōu)劣勢時提到了過程間分析這種大壓力的分析算法正是即時編譯的弱項??梢栽囅胍幌?,如果逃逸分析完畢后發(fā)現(xiàn)幾乎找不到幾個不逃逸的對象,那這些運行期耗用的時間就白白浪費了,所以目前虛擬機只能采用不那么準確,但時間壓力相對較小的算法來完成分析。
????????C和 C++ 語言里面原生就支持了棧上分配(不使用 new 操作符即可),而 C# 也支持值類型,可以很自然地做到標量替換(但并不會對引用類型做這種優(yōu)化)。在靈活運用棧內(nèi)存方面,確實是Java 的一個弱項。在現(xiàn)在仍處于實驗階段的Valhalla 項目里,設(shè)計了新的 inline 關(guān)鍵字用于定義 Java 的內(nèi)聯(lián)類型,目的是實現(xiàn)與C# 中值類型相對標的功能。有了這個標識與約束,以后逃逸分析做起來就會簡單很多。
????????下面筆者將通過一系列Java 偽代碼的變化過程來模擬逃逸分析是如何工作的,向讀者展示逃逸分析能夠?qū)崿F(xiàn)的效果。初始代碼如下所示:
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????此處筆者省略了Point 類的代碼,這就是一個包含 x y 坐標的 POJO 類型,讀者應(yīng)該很容易想象它的樣子。
第一步,將 Point 的構(gòu)造函數(shù)和 getX() 方法進行內(nèi)聯(lián)優(yōu)化:
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????第二步,經(jīng)過逃逸分析,發(fā)現(xiàn)在整個test() 方法的范圍內(nèi) Point 對象實例不會發(fā)生任何程度的逃逸, 這樣可以對它進行標量替換優(yōu)化,把其內(nèi)部的x y 直接置換出來,分解為 test() 方法內(nèi)的局部變量,從而避免Point 對象實例被實際創(chuàng)建,優(yōu)化后的結(jié)果如下所示:
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????第三步,通過數(shù)據(jù)流分析,發(fā)現(xiàn)py 的值其實對方法不會造成任何影響,那就可以放心地去做無效代碼消除得到最終優(yōu)化結(jié)果,如下所示:

?Java編譯器中的優(yōu)化技術(shù),JVM,java

????????從測試結(jié)果來看,實施逃逸分析后的程序在MicroBenchmarks 中往往能得到不錯的成績,但是在實際的應(yīng)用程序中,尤其是大型程序中反而發(fā)現(xiàn)實施逃逸分析可能出現(xiàn)效果不穩(wěn)定的情況,或分析過程耗時但卻無法有效判別出非逃逸對象而導(dǎo)致性能(即時編譯的收益)下降,所以曾經(jīng)在很長的一段時間里,即使是服務(wù)端編譯器,也默認不開啟逃逸分析 [2] ,甚至在某些版本(如 JDK 6 Update 18 )中還曾經(jīng)完全禁止了這項優(yōu)化,一直到JDK 7 時這項優(yōu)化才成為服務(wù)端編譯器默認開啟的選項。如果有需要,或者確認對程序運行有益,用戶也可以使用參數(shù)-XX +DoEscapeAnalysis 來手動開啟逃逸分析, 開啟之后可以通過參數(shù)-XX +PrintEscapeAnalysis 來查看分析結(jié)果。有了逃逸分析支持之后,用戶可以使用參數(shù)-XX +EliminateAllocations 來開啟標量替換,使用 +XX +EliminateLocks 來開啟同步消除,使用參數(shù)-XX +PrintEliminateAllocations 查看標量的替換情況。
????????盡管目前逃逸分析技術(shù)仍在發(fā)展之中,未完全成熟,但它是即時編譯器優(yōu)化技術(shù)的一個重要前進方向,在日后的Java 虛擬機中,逃逸分析技術(shù)肯定會支撐起一系列更實用、有效的優(yōu)化技術(shù)。

四、公共子表達式消除

????????公共子表達式消除是一項非常經(jīng)典的、普遍應(yīng)用于各種編譯器的優(yōu)化技術(shù),它的含義是:如果一個表達式E 之前已經(jīng)被計算過了,并且從先前的計算到現(xiàn)在 E 中所有變量的值都沒有發(fā)生變化,那么 E的這次出現(xiàn)就稱為公共子表達式。對于這種表達式,沒有必要花時間再對它重新進行計算,只需要直 接用前面計算過的表達式結(jié)果代替E 。如果這種優(yōu)化僅限于程序基本塊內(nèi),便可稱為局部公共子表達 式消除(Local Common Subexpression Elimination ),如果這種優(yōu)化的范圍涵蓋了多個基本塊,那就稱為全局公共子表達式消除(Global Common Subexpression Elimination )。下面舉個簡單的例子來說明它 的優(yōu)化過程,假設(shè)存在如下代碼:
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????如果這段代碼交給Javac 編譯器則不會進行任何優(yōu)化,那生成的代碼將如代碼清單 11-12 所示,是完全遵照Java 源碼的寫法直譯而成的。
代碼清單 11-12 未作任何優(yōu)化的字節(jié)碼
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????當(dāng)這段代碼進入虛擬機即時編譯器后,它將進行如下優(yōu)化:編譯器檢測到c*b b*c 是一樣的表達式,而且在計算期間b c 的值是不變的。
因此這條表達式就可能被視為:
Java編譯器中的優(yōu)化技術(shù),JVM,java
????????這時候,編譯器還可能(取決于哪種虛擬機的編譯器以及具體的上下文而定)進行另外一種優(yōu)化 ——代數(shù)化簡( Algebraic Simplification ),在 E 本來就有乘法運算的前提下,把表達式變?yōu)椋?
Java編譯器中的優(yōu)化技術(shù),JVM,java

?五、數(shù)組邊界檢查消除

????????數(shù)組邊界檢查消除(Array Bounds Checking Elimination )是即時編譯器中的一項語言相關(guān)的經(jīng)典優(yōu)化技術(shù)。我們知道Java 語言是一門動態(tài)安全的語言,對數(shù)組的讀寫訪問也不像 C 、 C++ 那樣實質(zhì)上就是裸指針操作。如果有一個數(shù)組foo[] ,在 Java 語言中訪問數(shù)組元素 foo[i] 的時候系統(tǒng)將會自動進行上下界的范圍檢查,即i 必須滿足 “i>=0&&i<foo.length” 的訪問條件,否則將拋出一個運行時異常: java.lang.ArrayIndexOutOfBoundsException。這對軟件開發(fā)者來說是一件很友好的事情,即使程序員沒有專門編寫防御代碼,也能夠避免大多數(shù)的溢出攻擊。但是對于虛擬機的執(zhí)行子系統(tǒng)來說,每次數(shù)組元素的讀寫都帶有一次隱含的條件判定操作,對于擁有大量數(shù)組訪問的程序代碼,這必定是一種性能負擔(dān)。
????????無論如何,為了安全,數(shù)組邊界檢查肯定是要做的,但數(shù)組邊界檢查是不是必須在運行期間一次不漏地進行則是可以“ 商量 的事情。例如下面這個簡單的情況:數(shù)組下標是一個常量,如 foo[3] ,只要在編譯期根據(jù)數(shù)據(jù)流分析來確定foo.length 的值,并判斷下標 “3” 沒有越界,執(zhí)行的時候就無須判斷了。更加常見的情況是,數(shù)組訪問發(fā)生在循環(huán)之中,并且使用循環(huán)變量來進行數(shù)組的訪問。如果編譯器只要通過數(shù)據(jù)流分析就可以判定循環(huán)變量的取值范圍永遠在區(qū)間[0 , foo.length) 之內(nèi),那么在循環(huán)中就可以把整個數(shù)組的上下界檢查消除掉,這可以節(jié)省很多次的條件判斷操作。??
????????把這個數(shù)組邊界檢查的例子放在更高的視角來看,大量的安全檢查使編寫Java 程序比編寫 C
C++ 程序容易了很多,比如:數(shù)組越界會得到 ArrayIndexOutOfBoundsException 異常;空指針訪問會得到NullPointException 異常;除數(shù)為零會得到 ArithmeticException 異常 …… C C++ 程序中出現(xiàn)類似的問題,一個不小心就會出現(xiàn)Segment Fault 信號或者 Windows 編程中常見的 “XXX 內(nèi)存不能為Read/Write”之類的提示,處理不好程序就直接崩潰退出了。但這些安全檢查也導(dǎo)致出現(xiàn)相同的程序, 從而使Java C C++ 要做更多的事情(各種檢查判斷),這些事情就會導(dǎo)致一些隱式開銷,如果不處 理好它們,就很可能成為一項“Java 語言天生就比較慢 的原罪。為了消除這些隱式開銷,除了如數(shù)組邊界檢查優(yōu)化這種盡可能把運行期檢查提前到編譯期完成的思路之外,還有一種避開的處理思路—— 隱式異常處理,Java 中空指針檢查和算術(shù)運算中除數(shù)為零的檢查都采用了這種方案。舉個例子,程序中訪問一個對象(假設(shè)對象叫foo )的某個屬性(假設(shè)屬性叫 value ),那以 Java 偽代碼來表示虛擬機訪問foo.value 的過程為:
Java編譯器中的優(yōu)化技術(shù),JVM,java

在使用隱式異常優(yōu)化之后,虛擬機會把上面的偽代碼所表示的訪問過程變?yōu)槿缦聜未a:?

Java編譯器中的優(yōu)化技術(shù),JVM,java
????????虛擬機會注冊一個Segment Fault 信號的異常處理器(偽代碼中的 uncommon_trap() ,務(wù)必注意這里 是指進程層面的異常處理器,并非真的Java try-catch 語句的異常處理器),這樣當(dāng) foo 不為空的時 候,對value 的訪問是不會有任何額外對 foo 判空的開銷的,而代價就是當(dāng) foo 真的為空時,必須轉(zhuǎn)到異 常處理器中恢復(fù)中斷并拋出NullPointException 異常。進入異常處理器的過程涉及進程從用戶態(tài)轉(zhuǎn)到內(nèi) 核態(tài)中處理的過程,結(jié)束后會再回到用戶態(tài),速度遠比一次判空檢查要慢得多。當(dāng)foo 極少為空的時 候,隱式異常優(yōu)化是值得的,但假如foo 經(jīng)常為空,這樣的優(yōu)化反而會讓程序更慢。幸好 HotSpot 虛擬機足夠聰明,它會根據(jù)運行期收集到的性能監(jiān)控信息自動選擇最合適的方案。

?

到了這里,關(guān)于Java編譯器中的優(yōu)化技術(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • C/C++編譯器全局優(yōu)化技術(shù):全局優(yōu)化是針對整個程序進行的優(yōu)化,包括函數(shù)之間的優(yōu)化

    編程語言的設(shè)計和實現(xiàn)與人類心理學(xué)有著密切的聯(lián)系。C++編譯器全局優(yōu)化技術(shù)在這個領(lǐng)域中是一個重要的例子。在這篇博客中,我們將從心理學(xué)的角度來探討C++編譯器全局優(yōu)化技術(shù)的原理和實踐。 人類大腦的神經(jīng)網(wǎng)絡(luò)在處理信息時,會自動進行優(yōu)化以提高效率。我們的思維和

    2023年04月26日
    瀏覽(46)
  • 探索Kotlin K2編譯器和Java編譯器的功能和能力

    文章首發(fā)地址 Kotlin K2編譯器是Kotlin語言的編譯器,負責(zé)將Kotlin源代碼轉(zhuǎn)換為Java字節(jié)碼或者其他目標平臺的代碼。K2編譯器是Kotlin語言的核心組件之一,它的主要功能是將Kotlin代碼編譯為可在JVM上運行的字節(jié)碼。 編譯過程: Kotlin K2編譯器將Kotlin源代碼作為輸入,并經(jīng)過詞法分

    2024年02月11日
    瀏覽(23)
  • C/C++編譯器內(nèi)存優(yōu)化技術(shù):內(nèi)存優(yōu)化關(guān)注程序?qū)?nèi)存的訪問和使用,以提高內(nèi)存訪問速度和減少內(nèi)存占用。

    在日常生活中,我們常常會為了提高效率、節(jié)省資源而進行各種優(yōu)化。而在計算機領(lǐng)域,優(yōu)化是至關(guān)重要的一環(huán),尤其是當(dāng)涉及到編程語言和編譯器時。本文將從心理學(xué)的角度,帶您領(lǐng)略C++編譯器內(nèi)存優(yōu)化技術(shù)的奧秘,并引導(dǎo)您深入學(xué)習(xí)這一技術(shù)。 正如心理學(xué)家所研究的,人

    2023年04月22日
    瀏覽(19)
  • Lightly —— Java輕量級在線編譯器

    Lightly —— Java輕量級在線編譯器

    Lightly,一款輕量級在線集成開發(fā)工具(IDE)。 Lightly,支持客戶端和云端在線開發(fā)模式。 Lightly,支持項目實時協(xié)作、共同開發(fā)。 初始界面——引導(dǎo) ? ? ? ? ?了解Lightly IDE提供多種語言的項目編譯環(huán)境以及數(shù)據(jù)庫云端存儲,其為兩大重要特性。 支持的語言編譯環(huán)境(13):

    2024年04月27日
    瀏覽(23)
  • 形象談JVM-第二章-認識編譯器

    形象談JVM-第二章-認識編譯器

    我在上一章《形象談JVM-第一章-認識JVM》提到的“翻譯”,其實就是我們今天所說的“編譯”的概念。 上一章原文鏈接:https://www.cnblogs.com/xingxiangtan/p/17617654.html 原文: 【 虛擬機的職責(zé)是將字節(jié)碼翻譯成對應(yīng)系統(tǒng)能夠識別并執(zhí)行的機器碼, 比如在linux系統(tǒng),java文件被javac編譯

    2024年02月13日
    瀏覽(23)
  • JVM執(zhí)行引擎——解釋器與編譯器JIT

    ????????執(zhí)行引擎是JVM核心的組成部分之一,因為字節(jié)碼文件不能直接運行在操作系統(tǒng)上,所以執(zhí)行引擎就充當(dāng)了將字節(jié)碼文件翻譯為機器碼,是將高級語言轉(zhuǎn)化為機器語言的橋梁。 ????????執(zhí)行引擎有兩種行為方式:解釋執(zhí)行和編譯執(zhí)行。 ????????解釋器:當(dāng)J

    2024年02月15日
    瀏覽(21)
  • 編譯器的過度優(yōu)化

    編譯器在進行優(yōu)化的時候,可能為了效率而交換不相關(guān)的兩條相鄰指令的執(zhí)行順序。也就是指令重排,這也就引發(fā)了一些問題,下面就帶大家看兩個經(jīng)典的問題。 第一個例子來自單例模式的雙加鎖,下面是典型的雙加鎖的單例模式代碼: 上面的代碼看起來沒問題,并且采用

    2023年04月21日
    瀏覽(22)
  • 【jvm系列-07】深入理解執(zhí)行引擎,解釋器、JIT即時編譯器

    【jvm系列-07】深入理解執(zhí)行引擎,解釋器、JIT即時編譯器

    JVM系列整體欄目 內(nèi)容 鏈接地址 【一】初識虛擬機與java虛擬機 https://blog.csdn.net/zhenghuishengq/article/details/129544460 【二】jvm的類加載子系統(tǒng)以及jclasslib的基本使用 https://blog.csdn.net/zhenghuishengq/article/details/129610963 【三】運行時私有區(qū)域之虛擬機棧、程序計數(shù)器、本地方法棧 https

    2024年02月01日
    瀏覽(30)
  • ARM嵌入式編譯器編譯優(yōu)化選項 -O

    Arm嵌入式編譯器可以執(zhí)行一些優(yōu)化來減少代碼量并提高應(yīng)用程序的性能。不同的優(yōu)化級別有不同的優(yōu)化目標,不僅如此,針對某個目標進行優(yōu)化會對其他目標產(chǎn)生影響。比如想減小生成的代碼量,勢必會影響到該代碼的性能。所以優(yōu)化級別總是這些不同目標(代碼量,程序性

    2024年02月16日
    瀏覽(22)
  • 一個關(guān)于編譯器優(yōu)化選項問題的解決

    一個關(guān)于編譯器優(yōu)化選項問題的解決

    因為當(dāng)前項目單片機容量不夠使用,打算開啟編譯器優(yōu)化,結(jié)果在使用KEIL編譯器優(yōu)化后,程序在發(fā)送Modbus數(shù)據(jù)時,程序直接跑飛了 最后發(fā)現(xiàn)是 局部變量指針 作為了DMA的內(nèi)存地址參數(shù),導(dǎo)致當(dāng)DMA連續(xù)搬運數(shù)據(jù)時,實際那個局部變量已經(jīng)被釋放,導(dǎo)致DMA搬運數(shù)據(jù)的過程中出現(xiàn)錯

    2024年04月09日
    瀏覽(38)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包