小師妹學(xué)JVM之:深入理解JIT和編譯優(yōu)化-你看不懂系列

簡介

小師妹已經(jīng)學(xué)完JVM的簡單部分了管毙,接下來要進(jìn)入的是JVM中比較晦澀難懂的概念背桐,這些概念是那么的枯燥乏味关拒,甚至還有點惹人討厭请契,但是要想深入理解JVM,這些概念是必須的夏醉,我將會盡量嘗試用簡單的例子來解釋它們爽锥,但一定會有人看不懂,沒關(guān)系畔柔,這個系列本不是給所有人看的氯夷。

更多精彩內(nèi)容且看:

JIT編譯器

小師妹:F師兄,我的基礎(chǔ)已經(jīng)打牢了嗎靶擦?可以進(jìn)入這么復(fù)雜的內(nèi)容環(huán)節(jié)了嗎腮考?

小師妹不試試怎么知道不行呢?了解點深入內(nèi)容可以幫助你更好的理解之前的知識⌒叮現(xiàn)在我們開始吧踩蔚。

上次我們在講java程序的處理流程的時候,還記得那通用的幾步吧枚粘。

小師妹:當(dāng)然記得了馅闽,編寫源代碼,javac編譯成字節(jié)碼,加載到JVM中執(zhí)行福也。

image

對局骤,其實在JVM的執(zhí)行引擎中,有三個部分:解釋器暴凑,JIT編譯器和垃圾回收器峦甩。

image

解釋器會將前面編譯生成的字節(jié)碼翻譯成機器語言,因為每次都要翻譯现喳,相當(dāng)于比直接編譯成機器碼要多了一步凯傲,所以java執(zhí)行起來會比較慢。

為了解決這個問題嗦篱,JVM引入了JIT(Just-in-Time)編譯器泣洞,將熱點代碼編譯成為機器碼。

Tiered Compilation分層編譯

小師妹你知道嗎默色?在JDK8之前球凰,HotSpot VM又分為三種。分別是 client VM, server VM, 和 minimal VM腿宰,分別用在客戶端呕诉,服務(wù)器,和嵌入式系統(tǒng)吃度。

但是隨著硬件技術(shù)的發(fā)展甩挫,這些硬件上面的限制都不是什么大事了。所以從JDK8之后椿每,已經(jīng)不再區(qū)分這些VM了伊者,現(xiàn)在統(tǒng)一使用VM的實現(xiàn)來替代他們。

小師妹间护,你覺得Client VM和Server VM的本質(zhì)區(qū)別在哪一部分呢亦渗?

小師妹,編譯成字節(jié)碼應(yīng)該都是使用javac汁尺,都是同樣的命令法精,字節(jié)碼上面肯定是一樣的。難點是在執(zhí)行引擎上面的不同痴突?

說的對搂蜓,因為Client VM和Server VM的出現(xiàn),所以在JIT中出現(xiàn)了兩種不同的編譯器辽装,C1 for Client VM帮碰, C2 for Server VM。

因為javac的編譯只能做少量的優(yōu)化拾积,其實大量的動態(tài)優(yōu)化是在JIT中做的殉挽。C2相對于C1丰涉,其優(yōu)化的程度更深,更加激進(jìn)此再。

為了更好的提升編譯效率,JVM在JDK7中引入了分層編譯Tiered compilation的概念玲销。

對于JIT本身來說输拇,動態(tài)編譯是需要占用用戶內(nèi)存空間的,有可能會造成較高的延遲贤斜。

對于Server服務(wù)器來說策吠,因為代碼要服務(wù)很多個client,所以磨刀不誤砍柴工瘩绒,短暫的延遲帶來永久的收益猴抹,聽起來是可以接受的。

Server端的JIT編譯也不是立馬進(jìn)行的锁荔,它可能需要收集到足夠多的信息之后蟀给,才進(jìn)行編譯。

而對于Client來說阳堕,延遲帶來的性能影響就需要進(jìn)行考慮了跋理。和Server相比,它只進(jìn)行了簡單的機器碼的編譯恬总。

為了滿足不同層次的編譯需求前普,于是引入了分層編譯的概念。

大概來說分層編譯可以分為三層:

  1. 第一層就是禁用C1和C2編譯器壹堰,這個時候沒有JIT進(jìn)行拭卿。
  2. 第二層就是只開啟C1編譯器,因為C1編譯器只會進(jìn)行一些簡單的JIT優(yōu)化贱纠,所以這個可以應(yīng)對常規(guī)情況峻厚。
  3. 第三層就是同時開啟C1和C2編譯器。

在JDK7中谆焊,你可以使用下面的命令來開啟分層編譯:

-XX:+TieredCompilation

而在JDK8之后目木,恭喜你,分層編譯已經(jīng)是默認(rèn)的選項了懊渡,不用再手動開啟刽射。

OSR(On-Stack Replacement)

小師妹:F師兄,你剛剛講到Server的JIT不是立馬就進(jìn)行編譯的剃执,它會等待一定的時間來搜集所需的信息誓禁,那么代碼不是要從字節(jié)碼轉(zhuǎn)換成機器碼?

對的肾档,這個過程就叫做OSR(On-Stack Replacement)摹恰。為什么叫OSR呢辫继?我們知道JVM的底層實現(xiàn)是一個棧的虛擬機,所以這個替換實際上是一系列的Stack操作俗慈。

image

上圖所示姑宽,m1方法從最初的解釋frame變成了后面的compiled frame。

Deoptimization

這個世界是平衡的闺阱,有陰就有陽炮车,有優(yōu)化就有反優(yōu)化。

小師妹:F師兄酣溃,為什么優(yōu)化了之后還要反優(yōu)化呢瘦穆?這樣對性能不是下降了嗎?

通常來說是這樣的赊豌,但是有些特殊的情況下面扛或,確實是需要進(jìn)行反優(yōu)化的。

下面是比較常見的情況:

  1. 需要調(diào)試的情況

如果代碼正在進(jìn)行單個步驟的調(diào)試碘饼,那么之前被編譯成為機器碼的代碼需要反優(yōu)化回來熙兔,從而能夠調(diào)試。

  1. 代碼廢棄的情況

當(dāng)一個被編譯過的方法艾恼,因為種種原因不可用了黔姜,這個時候就需要將其反優(yōu)化。

  1. 優(yōu)化之前編譯的代碼

有可能出現(xiàn)之前優(yōu)化過的代碼可能不夠完美蒂萎,需要重新優(yōu)化的情況秆吵,這種情況下同樣也需要進(jìn)行反優(yōu)化。

常見的編譯優(yōu)化舉例

除了JIT編譯成機器碼之外五慈,JIT還有一下常見的代碼優(yōu)化方式纳寂,我們來一一介紹。

Inlining內(nèi)聯(lián)

舉個例子:

int a = 1;
int b = 2;
int result = add(a, b);
...
public int add(int x, int y) { return x + y; }
int result = a + b; //內(nèi)聯(lián)替換

上面的add方法可以簡單的被替換成為內(nèi)聯(lián)表達(dá)式泻拦。

Branch Prediction分支預(yù)測

通常來說對于條件分支毙芜,因為需要有一個if的判斷條件,JVM需要在執(zhí)行完畢判斷條件争拐,得到返回結(jié)果之后腋粥,才能夠繼續(xù)準(zhǔn)備后面的執(zhí)行代碼,如果有了分支預(yù)測架曹,那么JVM可以提前準(zhǔn)備相應(yīng)的執(zhí)行代碼隘冲,如果分支檢查成功就直接執(zhí)行,省去了代碼準(zhǔn)備的步驟绑雄。

比如下面的代碼:

// make an array of random doubles 0..1
double[] bigArray = makeBigArray();
for (int i = 0; i < bigArray.length; i++)
{
 double cur = bigArray[i];
 if (cur > 0.5) { doThis();} else { doThat();}
}

Loop unswitching

如果我們在循環(huán)語句里面添加了if語句展辞,為了提升并發(fā)的執(zhí)行效率,可以將if語句從循環(huán)中提取出來:

  int i, w, x[1000], y[1000];
  for (i = 0; i < 1000; i++) {
    x[i] += y[i];
    if (w)
      y[i] = 0;
  }

可以改為下面的方式:

  int i, w, x[1000], y[1000];
  if (w) {
    for (i = 0; i < 1000; i++) {
      x[i] += y[i];
      y[i] = 0;
    }
  } else {
    for (i = 0; i < 1000; i++) {
      x[i] += y[i];
    }
  }

Loop unrolling展開

在循環(huán)語句中万牺,因為要不斷的進(jìn)行跳轉(zhuǎn)罗珍,所以限制了執(zhí)行的速度洽腺,我們可以對循環(huán)語句中的邏輯進(jìn)行適當(dāng)?shù)恼归_:

 int x;
 for (x = 0; x < 100; x++)
 {
     delete(x);
 }

轉(zhuǎn)變?yōu)椋?/p>

 int x; 
 for (x = 0; x < 100; x += 5 )
 {
     delete(x);
     delete(x + 1);
     delete(x + 2);
     delete(x + 3);
     delete(x + 4);
 }

雖然循環(huán)體變長了,但是跳轉(zhuǎn)次數(shù)變少了覆旱,其實是可以提升執(zhí)行速度的蘸朋。

Escape analysis逃逸分析

什么叫逃逸分析呢?簡單點講就是分析這個線程中的對象扣唱,有沒有可能會被其他對象或者線程所訪問藕坯,如果有的話,那么這個對象應(yīng)該在Heap中分配画舌,這樣才能讓對其他的對象可見堕担。

如果沒有其他的對象訪問已慢,那么完全可以在stack中分配這個對象曲聂,棧上分配肯定比堆上分配要快,因為不用考慮同步的問題佑惠。

我們舉個例子:

  public static void main(String[] args) {
    example();
  }
  public static void example() {
    Foo foo = new Foo(); //alloc
    Bar bar = new Bar(); //alloc
    bar.setFoo(foo);
  }
}

class Foo {}

class Bar {
  private Foo foo;
  public void setFoo(Foo foo) {
    this.foo = foo;
  }
}

上面的例子中朋腋,setFoo引用了foo對象,如果bar對象是在heap中分配的話膜楷,那么引用的foo對象就逃逸了旭咽,也需要被分配在heap空間中。

但是因為bar和foo對象都只是在example方法中調(diào)用的赌厅,所以穷绵,JVM可以分析出來沒有其他的對象需要引用他們,那么直接在example的方法棧中分配這兩個對象即可特愿。

逃逸分析還有一個作用就是lock coarsening仲墨。

為了在多線程環(huán)境中保證資源的有序訪問,JVM引入了鎖的概念揍障,雖然鎖可以保證多線程的有序執(zhí)行目养,但是如果實在單線程環(huán)境中呢?是不是還需要一直使用鎖呢毒嫡?

比如下面的例子:

public String getNames() {
     Vector<String> v = new Vector<>();
     v.add("Me");
     v.add("You");
     v.add("Her");
     return v.toString();
}

Vector是一個同步對象癌蚁,如果是在單線程環(huán)境中,這個同步鎖是沒有意義的兜畸,因此在JDK6之后努释,鎖只在被需要的時候才會使用。

這樣就能提升程序的執(zhí)行效率咬摇。

總結(jié)

本文介紹了JIT的原理和一些基本的優(yōu)化方式洽洁。后面我們會繼續(xù)探索JIT和JVM的秘密,敬請期待菲嘴。

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/jvm-jit-in-detail/

本文來源:flydean的博客

歡迎關(guān)注我的公眾號:程序那些事饿自,更多精彩等著您汰翠!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市昭雌,隨后出現(xiàn)的幾起案子复唤,更是在濱河造成了極大的恐慌,老刑警劉巖烛卧,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛纫,死亡現(xiàn)場離奇詭異,居然都是意外死亡总放,警方通過查閱死者的電腦和手機呈宇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來局雄,“玉大人甥啄,你說我怎么就攤上這事【娲睿” “怎么了蜈漓?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宫盔。 經(jīng)常有香客問我融虽,道長,這世上最難降的妖魔是什么灼芭? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任有额,我火速辦了婚禮,結(jié)果婚禮上彼绷,老公的妹妹穿的比我還像新娘巍佑。我一直安慰自己,他們只是感情好苛预,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布句狼。 她就那樣靜靜地躺著,像睡著了一般热某。 火紅的嫁衣襯著肌膚如雪腻菇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天昔馋,我揣著相機與錄音筹吐,去河邊找鬼。 笑死秘遏,一個胖子當(dāng)著我的面吹牛丘薛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邦危,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼洋侨,長吁一口氣:“原來是場噩夢啊……” “哼舍扰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起希坚,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤边苹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后裁僧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體个束,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年聊疲,在試婚紗的時候發(fā)現(xiàn)自己被綠了茬底。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡获洲,死狀恐怖阱表,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昌妹,我是刑警寧澤捶枢,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布握截,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗅虏。R本人自食惡果不足惜躲胳,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胯努。 院中可真熱鬧牢裳,春花似錦、人聲如沸叶沛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灰署。三九已至判帮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溉箕,已是汗流浹背晦墙。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肴茄,地道東北人晌畅。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像寡痰,于是被迫代替她去往敵國和親抗楔。 傳聞我的和親對象是個殘疾皇子棋凳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354