首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JVM 如何實現 Java 代碼高效執行:JRockit 視角下的 JIT 編譯、熱點探測與激進優化

     編輯:Java 相關知識

基於《Oracle JRockit: The Definitive Guide》(JRockit 兩位資深架構師著作,Marcus Hirt 為 Mission Control Leader)第二章《Adaptive Code Generation》核心內容,系統闡述頂級 JVM 如何將 Java 位元碼轉化為高效機器碼,涵蓋解釋執行 vs JIT、熱點探測三大流派、激進優化(內聯/逃逸分析/標量替換)、逆優化機制、JRockit 與 HotSpot 關鍵差異,為 JVM 進階學習者提供架構級視野。

從位元碼到機器碼:三種執行策略

  1. 純解釋執行:逐條翻譯位元碼為機器指令,效率極低。
  2. 即時編譯 (JIT):載入類時將位元碼編譯為本地機器碼。
    —— JRockit 選擇:面向 Server 應用,不在意啟動速度,追求極致執行效能,**無解釋器,啟動即編譯**。
    —— HotSpot 選擇:兼顧 Client/Server,**先解釋執行,熱點觸發後編譯**(Tiered Compilation)。
  3. AOT (提前編譯):構建期直接產出機器碼,犧牲跨平台性,非主流 JVM 路線。

熱點探測:三大技術路線

方法原理優缺點典型採用者
方法調用計數器 每次方法進入計數,達閾值觸發編譯 計數器頻繁 Cache Miss,微弱性能損耗 HotSpot
軟體採樣 定期暫停線程採樣 PC 指針 需暫停線程;優點:無需全方法計數 早期 JVM、部分商業 JVM
硬體採樣 (PMU) CPU 績效監控單元 (Intel IA-64 等) 硬體級採樣 零干擾、精準;缺點:硬體依賴 JRockit (支援 IA-64)、現代 JVM 結合 Linux perf

激進優化:賭熱點「穩定」

JIT 編譯器在熱點代碼上施加投機性優化,賭注:運行行為將持續符合當前觀察模式。

核心激進優化手段

  • 投機內聯:介面調用/虛方法調用 → 假設單一實現 → 直接內聯實現代碼。失效時逆優化回基礎版本。
  • 分支預測與異常路徑優化:熱點分支假設單向命中,異常拋出路徑假設不執行,極大簡化控制流。
  • 逃逸分析 + 標量替換:物件未逃逸方法/線程 → 拆解為標量局部變量(甚至寄存器分配),消除分配與同步開銷。
  • 鎖消除/粗化:逃逸分析證明鎖無競爭 → 移除同步;連續同步塊 → 合併為粗粒度鎖。

逆優化機制

假設失效(如新類載入打破單一實現假設) → On-Stack Replacement (OSR) 或棧幀替換 → 無縫切回解釋執行或基礎編譯版本。JRockit 未採用 OSR,認為收益不足;HotSpot 大量使用 OSR(影響 Benchmark 寫法)。

JIT 編譯器的兩大硬性職責

  1. 為 GC 提供精準物件映射表 (Oop Maps):掃描棧幀/寄存器精確識別物件引用,支援精準/移動式 GC。
  2. 為除錯/Profiling 保留源碼映射:行號、變量名、作用域,支援除錯器與 JFR/飛行記錄器。

位元碼混淆與優化的 JRockit 觀點

  • 名稱混淆:無害,可行。
  • 控制流混淆:有害,可能阻礙 JIT 控制流分析與優化(如內聯判斷、逃逸分析)。
  • 位元碼層面優化:意義有限,核心優化應在 JIT 階段完成(JIT 擁有完整運行時上下文)。

結語

JRockit 通過「啟動即編譯 + 硬體採樣 + 激進投機優化 + 無 OSR」路線,展示了 Java 執行效能理論上可超越 C/C++的可能性(運行時資訊優於靜態編譯)。HotSpot 則通過分層編譯平衡啟動與峰值。理解這兩條路線的設計哲學,是掌握 JVM 效能調優的認知基石。

 

《Oracle JRockit: The Definitive Guide》一書是由Oracle JRockit的兩位資深開發人員寫的,其中的Marcus Hirt更是JRockit Mission Control的leader,這本書詳細的對Oracle JRockit進行了介紹,最突出的特點非常系統化的介紹了一個JVM通常是如何實現的,而JRockit這樣一個極為優秀的JVM又是做了哪些優化,為什麼做這些優化,這本書對於對JVM感興趣的同學而言應該是必讀的一本書,其實即使對於JVM興趣不強的同學,裡面的優化思路的介紹也是值得學習,本系列的blog主要是總結看這本書得到的一些收獲,由於書中知識量巨大,因此得分成多篇blog來總結了。

書的第二章為:Adaptive Code Generation,在這章中作者向我們講解了一個優秀的JVM是如何來實現代碼的高效執行的,感興趣的同學其實可以在不看下面blog內容之前,先考慮下如果是你做的話,你會怎麼做來實現Java代碼的高效執行呢,然後再對比下這章的內容,我想你能學到很多的,:)

用過Java的同學都知道,Java是通過javac將Java源碼編譯為class文件,然後通過ClassLoader裝載此class文件,之後就可執行此class了,要最高效的執行這個class,最好的方法莫過於class文件直接就是機器碼,這樣直接執行就可以了,但Java是跨平台的,因此class文件就不能是機器碼了。

由於class文件不是直接的機器碼,要執行它最簡單的方法就是采用純粹的解釋方式,解釋方式由於每次都得將class文件中的指令翻譯為對應的機器環境的指令,效率是很低的。

為了能更高效的執行,同時又保持跨平台的特性,另外一個方法就是在執行class時再將其翻譯為對應的機器碼,這個方法是比較靠譜的,因此無論是Hotspot、還是JRockit,都采用了這種方式,也就是大家熟知的JIT(Just In Time) Compiler。

OK,既然覺得在裝載class後翻譯成機器碼去執行可以比較高效,那這個時候又會出現兩種狀況,是執行class的時候就立刻翻譯成機器碼,還是先用解釋模式執行,然後到一定時機再翻譯成機器碼呢,之所以出現這兩種狀況,原因在於將class翻譯為機器碼是需要消耗時間的,因此如果執行class的時候就立刻翻譯成機器碼的話,也就會導致Java程序啟動速度會比較慢,JRockit是這麼認為的,JRockit的服務對象是server級應用,這類應用的特點是沒那麼在乎啟動速度,而更在乎的是執行時的高效,而且如果執行的時候就立刻翻譯成機器碼的話,就意味著壓根不需要實現解釋器,因此JRockit采取的方法是在執行class時直接編譯為機器碼,而Hotspot由於需要同時支持client和server應用,對於client應用而言,啟動速度非常重要,因此Hotspot采用的是先解釋執行,到了一定時機後再翻譯成機器碼。

如果認為就這樣就完成了Java代碼的執行的實現,那就太小看JVM了,由於JVM能夠知道代碼運行的全部狀況,自然還可以做出更多更出色的提升代碼執行速度的優化,例如標量替換、更好的inline等,後面再來細說,因此這樣就出現了一個狀況,什麼時候對哪些代碼來做這些更猛的優化呢。

真正值得做更猛的優化的代碼自然是所謂的”熱點”代碼,如何來發現哪些代碼是熱點代碼呢,通常有三種方法:
1、方法調用計數器
方法調用計數器是常見的方式,hotspot采用的即為這種,這種方式不好的地方就在於計數器本身經常是cpu cache misses的,因此稍微會有點影響性能。
2、對線程進行采樣
可采用軟件或硬件方式來實現,軟件方式實現不好的地方在於采樣的時候需要暫停線程,好處是因為是采樣,不需要對所有方法進行計數,硬件方式自然是最好的,但不是所有的硬件都支持的,支持的硬件中最典型的是intel IA-64的CPU。

在有了發現熱點代碼的方法後,接下來需要做的就是更猛的優化,有很多種,例如Java的代碼中,通常會是接口方式的調用,但因為是接口方式的調用,所以其實默認情況下是不好做inline處理的,但JVM為了更高效的執行代碼,如發現這代碼為熱點代碼,那麼就會做一些激進的優化,例如會假設這個接口只有一個實現,然後就可以直接將此實現對應的代碼inline進來了(至於為什麼inline後效率更高,這個請參考編譯原理之類的書),這些激進優化同樣適合於if、拋異常這些狀況,當然,當激進優化的條件失效時,就會逆優化回到之前基本編譯的代碼。
而其他的更猛的優化還包括根據線程執行路徑進行逃逸分析等,後面再專門寫一篇blog來講解下一些翻譯為機器碼的優化吧,其實大多都是編譯原理的一些東西。

書中在介紹JRockit如何實現自己的JIT Compiler時,提到了Bytecode混淆以及bytecode優化,JRockit的態度是bytecode混淆時將name進行混淆是靠譜的,但如果對control flow進行混淆,就不太好了,因為這有可能會導致jit compile時的有些優化也做不了了,而bytecode優化,JRockit的態度是應該避免,因為沒什麼太大的意義,更主要的優化還是得靠jit compiler。

JRockit的JIT Compiler的實現和Hotspot另外一個很大的不同在於JRockit並未采用on-stack replacement,據JRockit的研究,這個沒有太大必要,當然,對於編寫benchmark代碼時則要注意這個不同。

JIT Compiler在compile時還需要考慮的幾個重點問題:
1、為GC提供必要的信息;
2、為查錯提供必要的信息,例如代碼的行數、變量名等;

從這章的內容可以看到,JRockit為了能夠讓Java代碼能夠高效的執行,是做出了非常多的努力的,也可以看到很多JRockit與Hotspot不同的地方,甚至可以看出Java代碼的執行比C代碼的執行高效都是有可能的,:)。
 

 
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved