[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

济南小老虎發表於2024-03-25
https://zhuanlan.zhihu.com/p/453437019

14 人贊同了該文章

今年四月五日,阿里雲開放了新一代ECS例項的邀測[1],Alibaba Dragonwell也在新ECS上進行了極致的最佳化。相比於之前的dragonwell_11.0.8.3版本,即將釋出的dragonwell_11.0.11.6在SPECjbb2015[2] composite模式測試中,系統吞吐量max-jOPS提升55%,響應時間約束下的系統吞吐量critical-jOPS提升602%。

如下圖所示,圖中資料做了歸一化處理,以11.0.8.3_GA的critical-jOPS為1個基準單位。測試環境:阿里雲80核,256g記憶體ECS例項,作業系統為Alinux3 [3]。

[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

Alibaba Dragonwell

過去的十幾年中,Java在阿里巴巴內部迅猛發展。阿里內使用Java語言編寫的應用越來越多,數萬的Java開發者每年產出超過十億行Java程式碼,這些程式碼都執行在阿里巴巴內部的OpenJDK定製版AJDK上。Alibaba Dragonwell是AJDK的開源版[4](github連結見文章末尾),使用和OpenJDK一樣的License,並永久免費。Alibaba Dragonwell有8和11兩個版本,於2019年開源,當時僅支援x86-64架構,在2020年擴充套件到AArch64平臺。

Alibaba Dragonwell結合阿里線上電商、金融、物流等各個業務場景做了大量細緻最佳化,新增了協程/多租戶/Jwarmup等諸多自研特性,並且在阿里雲超大規模的伺服器叢集上經受了長時間大規模的驗證。

調優方案與工具

由於SPECjbb2015動輒就需要兩個小時才能得到一次完整的跑分分數,為了壓榨效能調優單位時間內我們所能獲得的資訊量和效能試驗的效率,我們開發了自動測試平臺和效能分析工具來輔助SPECjbb2015效能調優,並且這套方法可以用在未來更多類似的效能調優案例中。自動測試平臺可以自動發起測試,並且在測試過程中呼叫基於perf的效能分析工具來採集CPU微架構資料以及系統熱點資料,從而收集到每次實驗過程中的關鍵效能資料,並將資料存檔以視覺化介面的形式展現,方便未來回顧和分析。同時為了避免SPECjbb2015單次實驗耗時長影響效率,跑效能實驗時我們採用了SPECjbb2015特殊的PRESET模式。該模式下可以指定壓力指定時間來啟動效能測試,不僅方便調優系統進行效能採集,還可以觀察在一定壓力下SPECjbb2015的系統熱點和微架構資料情況。我們透過該套調優系統獲取到了Alibaba Dragonwell和其他JDK在跑SPECjbb2015時的熱點和微架構資料,並且發現了諸多最佳化機會,如在GC熱點和暫停時間上有較為明顯的問題,從而深入到相關程式碼,並以效能資料為線索解決了相關的效能問題,具體的技術細節將在下文中向大家一一道來。

GC暫停時間最佳化

這項最佳化源於一個出人意料的發現,在SPECjbb2015中GC暫停時間竟然超過了總執行時間的20%,並且穩定復現。

透過上一小節中提到的調優系統,定位到出問題的是一個GC任務佇列相關函式,並且明確的指向了原子Compare and Swap(CAS)相關程式碼。

[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

新ECS採用的CPU架構中CAS主要有如下的兩種實現方式:

  • 使用帶load-aquire和store-release語義的指令對的實現方式
  • LSE指令集中的CAS專項指令

多數JVM在GC中使用第一種方法,然而第二種在高衝突的情況下效能更加出色,因此Dragonwell改變了編譯方式,使用LSE指令集實現CAS,有效的減少了暫停時間。下圖展示了最佳化效果,我們採集了SPECjbb2015執行在不同核數上的GC資料,並採用吞吐量作為衡量GC效能的指標。 吞吐量 = (執行時間 – Stop-The-World時間)/執行時間 * 100%

[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

我們可以看到最佳化前的CAS方式會造成吞吐量隨使用的核數增加而劇烈下降,在80核的情況下甚至不足80%,而使用LSE CAS後吞吐量穩定在99%以上。對這個最佳化的另外兩點補充說明:1. 此改動只針對JVM內部的CAS實現,不包括JIT(Just-In-Time)生成的程式碼。JIT會動態檢查硬體特性,在支援LSE指令集的系統上會優先使用LSE指令集。2. 除了使用LSE CAS外,改變GC佇列演算法減少CAS也可以達到減少暫停時間的效果,OpenJDK社群在新版本中採用了這種方法。不過兩種辦法並不衝突,Alibaba Dragonwell同時採用了兩種最佳化,達到了最優效果。

快速序列化

Alibaba Dragonwell在保證相容性基礎上對java原生序列化進行了最佳化,透過快取大幅提高了效能。透過分析發現, 原生序列化瓶頸大多在於大量的class 查詢,如在反序列化時需要獲取對端類定義的元資訊等。引入了一層透過類全限定名和類載入器對映到java類物件的快取,減少了大量Class.forName的呼叫。具體做法:在反序列化時獲取到類描述符,再根據類描述符查詢資訊時將會受限從classCache中查詢,命中則立即返回,如果沒有找到當前classloader和類全限定名唯一指定的類物件,將會走預設的類查詢流程並且將結果快取。同時, 在反序列化時會大量呼叫latestUserDefinedLoader 來查詢首個使用者定義的類載入器,因為此過程較重(涉及一次JNI呼叫和爬棧)也進行了快取。

指令融合

指令融合是指將多個指令使用效率更高的一條或者幾條指令進行替換從而提高效能。

Dragonwell對記憶體屏障/記憶體讀寫/比較跳轉等多個場景做了最佳化,由於篇幅限制而且此類最佳化原理較為類似,在此僅舉一例,三條指令融合成一條,如下圖所示。

[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

上面介紹了Alibaba Dragonwell內部的一些最佳化,下面我們換一個角度,從引數調優方面介紹對SPECjbb2015的最佳化。

大記憶體系統開啟壓縮指標

SPECjbb2015是一個記憶體敏感型的測試,壓縮指標對SPECjbb2015分數的提升非常明顯。不過預設情況下使用壓縮指標最大隻能用32g記憶體,這對80核的系統來說實在是太小了。其實透過適當的引數組合,我們完全可以在更大的記憶體中使用壓縮指標。

[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

首先我們瞭解下壓縮指標的基本原理。如上圖所示,由於Java物件有明確的對齊要求,因此物件的地址必然由數個0結尾,0的個數由對齊位數決定。省略java物件地址結尾的數個0可解決記憶體而且不會丟失有效地址資訊,需要訪問物件時可以透過補0獲得完整的地址。

由此可知,我們可以透過調整Java物件對齊位數控制壓縮指標生效的最大記憶體。預設情況下Java為8位元組對齊(3bit),加上壓縮指標本身的的32bit,最多隻能表示32g記憶體。但如果調整為32位元組對齊,那麼有37bit可以使用,也就是128g,這對於80核來說基本上夠用了。

分層編譯調優

分層編譯是JVM最基礎的機制之一,一般情況下對它改動比較少,不過在SPECjbb2015的場景下,在分層編譯上仍有調優空間。首先介紹下分層編譯。JVM在執行的時候動態的將位元組碼編譯成機器碼執行,JVM(hotspot)內部編譯引擎主要有三個:

  1. 直譯器:無編譯開銷,但解釋執行效率很低。
  2. C1編譯器:編譯開銷較低,生成程式碼質量一般。
  3. C2編譯器:生成程式碼質量很高,但編譯開銷很高。

這三個編譯引擎相互配合,執行次數較少的程式碼由直譯器和C1負責,C2只編譯熱點程式碼,從而讓Java可以達到峰值效能與編譯開銷的平衡,使應用執行更加平滑。不過分層編譯也有自己的缺點,一個較為明顯的問題是它會增大生成程式碼的總量。下圖展示SPECjbb2015執行時C1/C2編譯方法數目。

[轉帖]JVM效能提升50%,聊一聊背後的秘密武器Alibaba Dragonwell

圖中Level1-3均為C1編譯,根據收集執行資訊的力度不同分為了三個等級,Level4為C2編譯。我們可以看到C1編譯了70%的方法,因此關閉分層編譯,僅保留C2編譯器可以減少生成程式碼,從而一定程度上提高快取記憶體和葉表命中率。對於SPECjbb2015來說,由於分數只取決於最後幾分鐘的峰值處理能力,前面大概兩個小時的請求爬升階段都可以視作預熱,因此啟動期的編譯開銷並不關鍵。我們可以關閉分層編譯來減少生成程式碼,提高快取記憶體和列表命中率。最終在測試中發現關閉分層編譯生成程式碼總量由29M降低到9M,有明顯減少。本文總結了Alibaba Dragonwell的一些重要最佳化措施,請注意阿里承諾會持續的最佳化Dragonwell效能,同時更緊密地和OpenJDK等開源社群協作,貢獻更多的定製化特性,促進Java技術的持續發展。

相關文章