Java程序最初是通過解釋器進(jìn)行解釋執(zhí)行的痘绎,當(dāng)虛擬機發(fā)現(xiàn)某個方法或者代碼塊運行特別頻繁時兔毒,就會把這些代碼認(rèn)定為熱點代碼,為了提高熱點代碼的執(zhí)行效率纤怒,運行時虛擬機將會把熱點代碼編譯成與本地平臺相關(guān)的機器碼黄娘,并進(jìn)行各種層次的優(yōu)化峭状,完成這個任務(wù)的編譯器稱為即時編譯器克滴。即時編譯生成機器相關(guān)的中間碼,可重復(fù)執(zhí)行緩存效率高优床。解釋執(zhí)行直接執(zhí)行字節(jié)碼劝赔,重復(fù)執(zhí)行需要重復(fù)解釋。
即時編譯(JIT)與預(yù)編譯(AOT):在Dalvik下胆敞,應(yīng)用每次運行都需要通過即時編譯器(JIT)將字節(jié)碼轉(zhuǎn)換為機器碼着帽,即每次都要編譯加運行,這雖然會使安裝過程比較快移层,但是會拖慢應(yīng)用的運行效率仍翰。而在ART 環(huán)境中,應(yīng)用在第一次安裝的時候观话,字節(jié)碼就會預(yù)編譯(AOT)成機器碼予借,這樣的話,雖然設(shè)備和應(yīng)用的首次啟動(安裝慢了)會變慢频蛔,但是以后每次每次打開應(yīng)用灵迫,執(zhí)行的都是本地機器碼。移除了運行時的解釋執(zhí)行都可以直接運行晦溪,因此運行效率會提高瀑粥。Android7.0版本ART加入了即時編譯器JIT,作為AOT的一個補充三圆,在應(yīng)用程序安裝時并不會將字節(jié)碼全部編譯成機器碼利凑,在運行中將熱點代碼編譯成機器碼,從而縮短應(yīng)用程序的安裝時間并節(jié)省存儲空間嫌术。
解釋執(zhí)行:將編譯好的字節(jié)碼一行一行地翻譯為機器碼執(zhí)行。編譯執(zhí)行:以方法為單位牌借,將字節(jié)碼一次性翻譯為機器碼后執(zhí)行度气。當(dāng)程序需要迅速啟動和執(zhí)行時,解釋器可以首先發(fā)揮作用膨报,省去編譯的時間磷籍,立即執(zhí)行;當(dāng)程序運行后现柠,隨著時間的推移院领,編譯器逐漸會失去作用,把越來越多的代碼編譯成本地代碼后够吩,可以獲取更高的執(zhí)行效率比然。解釋執(zhí)行可以節(jié)約內(nèi)存,而編譯執(zhí)行可以提升效率周循。目前主流的HotSpot虛擬機中默認(rèn)是采用解釋器與其中一個編譯器直接配合的方式工作强法。
編譯對象與觸發(fā)條件
運行過程中會被即時編譯器編譯的熱點代有兩類:
(1)被多次調(diào)用的方法万俗。
(2)被多次調(diào)用的循環(huán)體。
以上兩種情況饮怯,編譯器都是以整個方法作為編譯對象闰歪,這種編譯也是虛擬機中標(biāo)準(zhǔn)的編譯方式。一段代碼或方法是不是熱點代碼蓖墅,是不是需要觸發(fā)即時編譯库倘,需要進(jìn)行Hot Spot Detection(熱點探測)。
目前主要的熱點 判定方式有以下兩種:
(1)基于采樣的熱點探測:虛擬機會周期性地檢查各個線程的棧頂论矾,如果發(fā)現(xiàn)某些方法經(jīng)常出現(xiàn)在棧頂教翩,那這段方法代碼就是“熱點代碼”。好處是:實現(xiàn)簡單高效拇囊,還可以很容易地獲取方法調(diào)用關(guān)系迂曲;缺點是:很難精確地確認(rèn)一個方法的熱度,容易因為受到線程阻塞或別的外界因素的影響而擾亂熱點探測寥袭。
(2)基于計數(shù)器的熱點探測:虛擬機會為每個方法路捧,甚至是代碼塊建立計數(shù)器,統(tǒng)計方法的執(zhí)行次數(shù)传黄,如果執(zhí)行次數(shù)超過一定的閥值杰扫,就認(rèn)為它是“熱點方法”。這種統(tǒng)計方法實現(xiàn)復(fù)雜一些膘掰,需要為每個方法建立并維護(hù)計數(shù)器章姓,而且不能直接獲取到方法的調(diào)用關(guān)系,但是它的統(tǒng)計結(jié)果相對更加精確嚴(yán)謹(jǐn)识埋。
在HotSpot虛擬機中使用的是第二種——基于計數(shù)器的熱點探測方法的兩個計數(shù)器:
(1)方法調(diào)用計數(shù)器:用來統(tǒng)計方法調(diào)用的次數(shù)凡伊,在默認(rèn)設(shè)置下,方法調(diào)用計數(shù)器統(tǒng)計的并不是方法被調(diào)用的絕對次數(shù)窒舟,而是一個相對的執(zhí)行頻率系忙,即一段時間內(nèi)方法被調(diào)用的次數(shù)。
(2)回邊計數(shù)器:用于統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù).
(準(zhǔn)確地說惠豺,應(yīng)該是回邊的次數(shù)银还,因為并非所有的循環(huán)都是回邊),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令就稱為“回邊”洁墙。
在確定虛擬機運行參數(shù)的前提下蛹疯,這兩個計數(shù)器都有一個確定的閥值,當(dāng)計數(shù)器的值超過了閥值热监,就會觸發(fā)JIT編譯捺弦。
即時編譯和解釋執(zhí)行的執(zhí)行順序:發(fā)了JIT編譯后,在默認(rèn)設(shè)置下,執(zhí)行引擎并不會同步等待編譯請求完成羹呵,而是繼續(xù)進(jìn)入解釋器按照解釋方式執(zhí)行字節(jié)碼骂际,直到提交的請求被編譯器編譯完成為止(編譯工作在后臺線程中進(jìn)行)。當(dāng)編譯工作完成后冈欢,下一次調(diào)用該方法或代碼時歉铝,就會使用已編譯的版本。方法調(diào)用計數(shù)器觸發(fā)即時編譯的流程:方法計數(shù)器觸發(fā)即時編譯的過程與回邊計數(shù)器觸發(fā)即時編譯的過程類似凑耻。方法調(diào)用計數(shù)器觸發(fā)即時編譯流程如下圖所示:
編譯優(yōu)化技術(shù)
以編譯方式執(zhí)行本地代碼比解釋方式更快太示,除去虛擬機執(zhí)行字節(jié)碼時額外消耗時間的原因外,還有一個重要原因香浩,虛擬機設(shè)計團(tuán)隊幾乎把對代碼的所有優(yōu)化措施都集中在了即時編譯器之中类缤,代碼優(yōu)化變換是建立在代碼的某種中間標(biāo)志或者機器碼之上,絕不是建立在java源碼之上的邻吭。華為方舟編譯器直接將代碼優(yōu)化從手機環(huán)節(jié)搬到了開發(fā)者環(huán)境餐弱,可以在很多地方對代碼進(jìn)行優(yōu)化,同時即時編譯器本地代碼比javac產(chǎn)生的的字節(jié)碼更優(yōu)秀囱晴。
即時編譯器針對不同的類型有不同的優(yōu)化技術(shù)膏蚓,具體可以參考《深入理解JVM》,幾種比較有代表性的優(yōu)化技術(shù)有:
(1)語言無關(guān)的經(jīng)典優(yōu)化技術(shù)之一:公共子表達(dá)式消除
int d = (C*B)*12 +a + (A+B*C)
int d = (E)*12 + a +A + E;
int d = 13E +2A;
以上就是公共子表達(dá)式消除畸写;
(2)語言相關(guān)的經(jīng)典優(yōu)化技術(shù)之一:數(shù)組范圍檢測消除:數(shù)組邊界檢查肯定是必須做的驮瞧,但是數(shù)組邊界檢查不是必須在運行期間一次不漏的檢查,如果通過數(shù)據(jù)流分析可以判斷循環(huán)變量的取值永遠(yuǎn)在數(shù)組邊界之內(nèi)枯芬,就可以在循環(huán)中將數(shù)組上下界檢測消除论笔。
(3)最重要的優(yōu)化技術(shù)之一:方法內(nèi)聯(lián):方法內(nèi)聯(lián)就是消除一些無用的代碼,如方法一已經(jīng)進(jìn)行了null判斷千所,如果調(diào)用方法二又進(jìn)行了null判斷狂魔,實際兩次null判斷僅需要一次。
(4)最前沿的優(yōu)化技術(shù)之一:逃逸分析:如果一個對象不會逃逸到方法或者線程之外淫痰,也就是別的方法或線程無法通過任何途徑訪問到這個對象毅臊,則可能為這個變量進(jìn)行一些高校的優(yōu)化,例如本來是堆上分配的變量發(fā)現(xiàn)沒有其他線程使用黑界,可以將其分配到棧上,棧上分配的對象就會隨著方法的結(jié)束而自動銷毀皂林。
(5)同步消除(synchronized):類似于鎖優(yōu)化朗鸠,變量不會逃逸到出方法,就可以將該方法上的同步鎖去掉础倍,相關(guān)內(nèi)容請參考《深入理解JVM》線程安全與鎖優(yōu)化章節(jié)烛占。