為什么說java語言是半解釋半編譯型語言?

java是一個半解釋半編譯型語言衰猛,早期java是通過解釋器來執(zhí)行迟蜜,效率低下;后期進(jìn)行優(yōu)化腕侄,解釋器在原本的c++字節(jié)碼解釋器基礎(chǔ)上小泉,擴(kuò)充了模板解釋器芦疏,效率有了明顯提升冕杠;后來又加入了JIT(即時編譯),效率就更加得到了提升酸茴。

解釋器與編譯器

解釋器與編譯器兩者各有優(yōu)勢:當(dāng)程序需要迅速啟動和執(zhí)行的時候分预,解釋器可以首先發(fā)揮作用,省去編譯的時間薪捍,立即執(zhí)行笼痹。在程序運行后,隨著時間的推移酪穿,編譯器逐漸發(fā)揮作用凳干,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率被济。當(dāng)程序發(fā)現(xiàn)運行環(huán)境中內(nèi)存資源限制較大救赐,可以使用解釋執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來提升效率只磷。同時经磅,解釋器還可以作為編譯器激進(jìn)優(yōu)化時的一個“逃生門”,讓編譯器根據(jù)概率選擇一些大多數(shù)時候都能提升運行速度的優(yōu)化手段钮追,當(dāng)激進(jìn)優(yōu)化的假設(shè)不成立预厌,如加載了新類后類型繼承結(jié)構(gòu)出現(xiàn)變化、出現(xiàn)“罕見陷阱”(Uncommon Trap)時可以通過逆優(yōu)化(Deoptimization)退回到解釋狀態(tài)繼續(xù)執(zhí)行(部分沒有解釋器的虛擬機(jī)中也會采用不進(jìn)行激進(jìn)優(yōu)化的C1編譯器擔(dān)當(dāng)“逃生門”的角色)元媚,因此轧叽,在整個虛擬機(jī)執(zhí)行架構(gòu)中苗沧,解釋器與編譯器經(jīng)常配合工作如下圖所示:


img

利用參數(shù)來指定虛擬機(jī)處于"解釋模式"、"編譯模式"還是"混合模式"犹芹。

混合模式:

java -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

解釋模式:

java -Xint -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, interpreted mode)

編譯模式:

java -Xcomp -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, compiled mode)

解釋器

字節(jié)碼解釋器

.java->javac->c++代碼->硬編碼(機(jī)器碼)

模板解釋器

.java->javac->硬編碼(機(jī)器碼)

編譯器

JIT即時編譯器

圖 2. 查看編譯模式

JIT編譯器分類

Client Compiler - C1編譯器

Client:-Client 模式啟動時崎页,速度較快,啟動之后不如 Server腰埂,適合用于桌面等有界面的程序

Server Compiler - C2編譯器

Server:-Server 模式啟動時飒焦,速度較慢,但是啟動之后屿笼,性能更高牺荠,適合運行服務(wù)器后臺程序

JIT編譯過程

當(dāng) JIT 編譯啟用時(默認(rèn)是啟用的),JVM 讀入.class 文件解釋后驴一,將其發(fā)給 JIT 編譯器休雌。JIT 編譯器將字節(jié)碼編譯成本機(jī)機(jī)器代碼,下圖展示了該過程肝断。

JIT 工作原理圖

熱點代碼(Hot)

理解

當(dāng)虛擬機(jī)發(fā)現(xiàn)某個方法或代碼塊的運行特別頻繁時杈曲,就會把這些代碼認(rèn)定為“熱點代碼”。

熱點代碼的分類

  • 被多次調(diào)用的方法

一個方法被調(diào)用得多了胸懈,方法體內(nèi)代碼執(zhí)行的次數(shù)自然就多担扑,成為“熱點代碼”是理所當(dāng)然的。

  • 被多次執(zhí)行的循環(huán)體

一個方法只被調(diào)用過一次或少量的幾次趣钱,但是方法體內(nèi)部存在循環(huán)次數(shù)較多的循環(huán)體涌献,這樣循環(huán)體的代碼也被重復(fù)執(zhí)行多次,因此這些代碼也應(yīng)該認(rèn)為是“熱點代碼”首有。

上面提到的多次是一個不具體的詞語燕垃,那到底是多少次才能成為熱點代碼呢?

如何檢測熱點代碼

判斷一段代碼是否是熱點代碼井联,是否需要觸發(fā)即使編譯卜壕,這樣的行為稱為熱點探測,熱點探測并不一定知道方法具體被調(diào)用了多少次烙常,目前主要的熱點探測判定方式有兩種:

  • 基于采樣的熱點探測:采用這種方法的虛擬機(jī)會周期性地檢查各個線程的棧頂如果發(fā)現(xiàn)某個(或某些)方法經(jīng)常出現(xiàn)在棧頂轴捎,那這個方法就是“熱點方法”

優(yōu)點:實現(xiàn)簡單高效,容易獲取方法調(diào)用關(guān)系(將調(diào)用堆棧展開即可)

缺點:不精確军掂,容易因為因為受到線程阻塞或別的外界因素的影響而擾亂熱點探測

  • 基于計數(shù)器的熱點探測:采用這種方法的虛擬機(jī)會為每個方法(甚至是代碼塊)建立計數(shù)器轮蜕,統(tǒng)計方法的執(zhí)行次數(shù),如果次數(shù)超過一定的閾值就認(rèn)為它是“熱點方法”

優(yōu)點:統(tǒng)計結(jié)果精確嚴(yán)謹(jǐn)

缺點:實現(xiàn)麻煩蝗锥,需要為每個方法建立并維護(hù)計數(shù)器跃洛,不能直接獲取到方法的調(diào)用關(guān)系

HotSpot使用第二種 - 基于計數(shù)器的熱點探測方法。

確定了檢測熱點代碼的方式终议,如何計算具體的次數(shù)呢汇竭?

計數(shù)器的種類(兩種共同協(xié)作)

  • 方法調(diào)用計數(shù)器:這個計數(shù)器用于統(tǒng)計方法被調(diào)用的次數(shù)葱蝗。默認(rèn)閾值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次
  • 回邊計數(shù)器:統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù)

了解了熱點代碼和計數(shù)器有什么用呢细燎?達(dá)到計數(shù)器的閾值會觸發(fā)后文講解的即時編譯两曼,也就是說即時編譯是需要達(dá)到某種條件才會觸發(fā)的,先寫結(jié)論玻驻,后文講解什么是即時編譯器悼凑。

兩個計數(shù)器的協(xié)作(這里討論的是方法調(diào)用計數(shù)器的情況):當(dāng)一個方法被調(diào)用時,會先檢查該方法是否存在被 JIT(后文講解) 編譯過的版本璧瞬,如果存在户辫,則優(yōu)先使用編譯后的本地代碼來執(zhí)行。如果不存在已被編譯過的版本嗤锉,則將此方法的調(diào)用計數(shù)器加 1渔欢,然后判斷方法調(diào)用計數(shù)器與回邊計數(shù)器之和是否超過方法調(diào)用計數(shù)器的閾值。如果已經(jīng)超過閾值瘟忱,那么將會向即時編譯器提交一個該方法的代碼編譯請求奥额。

當(dāng)編譯工作完成之后,這個方法的調(diào)用入口地址就會被系統(tǒng)自動改成新的访诱,下一次調(diào)用該方法時就會使用已編譯的版本垫挨。


image.png

分層編譯

原因:由于即時編譯器編譯本地代碼需要占用程序運行時間,要編譯出優(yōu)化程度更高的代碼盐数,所花費的時間可能越長棒拂;而且想要編譯出優(yōu)化程度更高的代碼伞梯,解釋器可能還要替編譯器收集性能監(jiān)控信息玫氢,這對解釋執(zhí)行的速度也有所影響,為了在程序啟動響應(yīng)速度與效率之間達(dá)到最佳平衡谜诫,HotSpot虛擬機(jī)將會逐漸啟用分層編譯漾峡,該概念在JDK1.6時期出現(xiàn),在JDK1.7的Server模式虛擬中作為默認(rèn)編譯策略被開啟喻旷。

層次:

  • 0:解釋代碼
  • 1:簡單C1編譯代碼
  • 2:受限的C1編譯代碼
  • 3:完全C1編譯代碼
  • 4:C2編譯代碼

查看和分析即時編譯結(jié)果

一般來說生逸,虛擬機(jī)的即時編譯過程對用戶程序是完全透明的,虛擬機(jī)通過解釋執(zhí)行代碼還是編譯執(zhí)行代碼且预,對于用戶來說沒有什么影響(執(zhí)行結(jié)果沒有差異槽袄,速度上會有很大的差異),大多數(shù)情況下用戶也沒有必要知道锋谐,但是虛擬機(jī)也提供了一些參數(shù)用來輸出即時編譯行為遍尺。

示例代碼:

public class Test {
    public static final int NUM = 15000;

    public  static int doubleValue(int i){
        return i * 2;
    }

    public static long calcSum(){
        long sum = 0;
        for (int i = 1; i <= 100; i++){
            sum += doubleValue(i);
        }
        return sum;
    }

    public static void main(String[] args) {
        for (int i = 0; i < NUM; i++){
            calcSum();
        }
    }
}

要知道某個方法是否被編譯過,可以使用參數(shù)-XX:+PrintCompilation要求虛擬機(jī)在即時編譯時將被編譯成本地代碼的方法名稱打印出來(帶%說明是由回邊計數(shù)器觸發(fā)的OSR編譯)

我們還可以加上-XX:+PrintInlining來要求虛擬機(jī)輸出方法內(nèi)聯(lián)信息(備注:-XX:+PrintInlining需要加-XX:+UnlockDiagnosticVMOptions)

    323  102       3       com.yirendai.lab.athenschool.jit.Test::doubleValue (4 bytes)
    323  103       1       com.yirendai.lab.athenschool.jit.Test::doubleValue (4 bytes)
    323  102       3       com.yirendai.lab.athenschool.jit.Test::doubleValue (4 bytes)   made not entrant
    323  104       3       com.yirendai.lab.athenschool.jit.Test::calcSum (26 bytes)
                              @ 12   com.yirendai.lab.athenschool.jit.Test::doubleValue (4 bytes)
    324  105 %     4       com.yirendai.lab.athenschool.jit.Test::calcSum @ 4 (26 bytes)
                              @ 12   com.yirendai.lab.athenschool.jit.Test::doubleValue (4 bytes)   inline (hot)
    325  106       4       com.yirendai.lab.athenschool.jit.Test::calcSum (26 bytes)
                              @ 12   com.yirendai.lab.athenschool.jit.Test::doubleValue (4 bytes)   inline (hot)
    327  104       3       com.yirendai.lab.athenschool.jit.Test::calcSum (26 bytes)   made not entrant
第一列含義為時間戳涮拗,第二列中的編號是編譯標(biāo)識乾戏,第三列為編譯級別
里面字符的參數(shù)含義:
b    Blocking compiler (always set for client)
*    Generating a native wrapper
%    On stack replacement (where the compiled code is running)
!    Method has exception handlers
s    Method declared as synchronized
n    Method declared as native
made non entrant    compilation was wrong/incomplete, no future callers will use this version
made zombie         code is not in use and ready for GC

@的含義:

A “place” in a Java method is defined by its bytecode index (BCI), and
the place that triggered an OSR compilation is called the “osr_bci”.
An OSR-compiled nmethod can only be entered from its osr_bci; there
can be multiple OSR-compiled versions of the same method at the same
time, as long as their osr_bci differ.

要理解made not entrant迂苛,不得不提codeCache

public int method(boolean flag) {
    if (flag) {
        return 1;
    } else {
        return 0;
    }
}

從解釋執(zhí)行的角度來看,他的執(zhí)行過程如下:

img

但經(jīng)過即時編譯器編譯后的代碼不一定是這樣鼓择,即時編譯器在編譯前會收集大量的執(zhí)行信息三幻,例如,如果這段代碼之前輸入的flag值都為true呐能,那么即時編譯器可能會將他變異成下面這樣:

public int method(boolean flag) {
    return 1;
}

即下圖這樣

img

但可能后面不總是flag=true念搬,一旦flag傳了false,這個錯了摆出,此時編譯器就會將他“去優(yōu)化”锁蠕,變成編譯執(zhí)行方式,在日志中的表現(xiàn)是made not entrant

為何 HotSpot 虛擬機(jī)要使用解釋器與編譯器并存的架構(gòu)懊蒸?

解釋器與編譯器兩者各有優(yōu)勢

解釋器:當(dāng)程序需要迅速啟動和執(zhí)行的時候荣倾,解釋器可以首先發(fā)揮作用,省去編譯的時間骑丸,立即執(zhí)行舌仍。

編譯器:在程序運行后,隨著時間的推移通危,編譯器逐漸發(fā)揮作用铸豁,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率菊碟。

兩者的協(xié)作:在程序運行環(huán)境中內(nèi)存資源限制較大時节芥,可以使用解釋執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來提升效率逆害。當(dāng)通過編譯器優(yōu)化時头镊,發(fā)現(xiàn)并沒有起到優(yōu)化作用,魄幕,可以通過逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行相艇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纯陨,隨后出現(xiàn)的幾起案子坛芽,更是在濱河造成了極大的恐慌,老刑警劉巖翼抠,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咙轩,死亡現(xiàn)場離奇詭異,居然都是意外死亡阴颖,警方通過查閱死者的電腦和手機(jī)活喊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膘盖,“玉大人胧弛,你說我怎么就攤上這事尤误。” “怎么了结缚?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵损晤,是天一觀的道長。 經(jīng)常有香客問我红竭,道長尤勋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任茵宪,我火速辦了婚禮最冰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘稀火。我一直安慰自己暖哨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布凰狞。 她就那樣靜靜地躺著篇裁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赡若。 梳的紋絲不亂的頭發(fā)上达布,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音逾冬,去河邊找鬼黍聂。 笑死,一個胖子當(dāng)著我的面吹牛身腻,可吹牛的內(nèi)容都是我干的产还。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼霸株,長吁一口氣:“原來是場噩夢啊……” “哼雕沉!你這毒婦竟也來了集乔?” 一聲冷哼從身側(cè)響起去件,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扰路,沒想到半個月后尤溜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡汗唱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年宫莱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哩罪。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡巡验,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碘耳,到底是詐尸還是另有隱情显设,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布辛辨,位于F島的核電站捕捂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斗搞。R本人自食惡果不足惜指攒,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望僻焚。 院中可真熱鬧允悦,春花似錦、人聲如沸虑啤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咐旧。三九已至驶鹉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铣墨,已是汗流浹背室埋。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留伊约,地道東北人姚淆。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像屡律,于是被迫代替她去往敵國和親腌逢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355