JVM C1、C2編譯器

Java虛擬機創建了C1和C2編譯器線程,用以優化應用程序的性能。但是有時這些線程會消耗大量CPU資源。在這篇文章中,我們將深入探討C1和C2編譯器線程,以及如何解決它們可能導致的高CPU消耗問題。

Hotspot JIT

HotSpot JIT(Just-In-Time)編譯器是Java虛擬機(JVM)中的即時編譯器,它負責將Java字節碼轉換爲本地機器代碼。HotSpot是Oracle JDK和OpenJDK中默認的JVM實現,它包含兩個主要的即時編譯器:C1(Client Compiler)和C2(Server Compiler)。

HotSpot JIT編譯器的工作方式是在運行時動態地將經常執行的字節碼編譯爲本地機器代碼,以提高程序的性能。它通過以下步驟實現:

  1. 解釋執行(Interpretation):初始階段,JVM會對Java字節碼進行解釋執行,不生成本地機器代碼,這使得程序可以立即運行。

  2. 即時編譯(Just-In-Time Compilation):當某段代碼(通常是被頻繁執行的熱點代碼)被識別爲性能瓶頸時,HotSpot JIT編譯器將這些熱點代碼編譯成本地機器代碼。這個過程包括對代碼進行優化,以提高執行速度。

  3. 本地機器代碼執行(Execution of Native Code):一旦代碼被編譯成本地機器代碼,JVM會直接執行這些代碼,而不是再次解釋執行對應的字節碼。

C1編譯器通常用於快速啓動和簡單的應用程序,因爲它生成的代碼速度較快,但優化程度較低。而C2編譯器更加激進,會花費更多時間進行更深層次的優化,生成更高效的本地機器代碼,適用於需要更高性能的場景。

HotSpot JIT編譯器的工作方式有助於提高Java程序的性能,因爲它能夠在運行時優化熱點代碼,將其轉換爲更高效的本地機器代碼,從而減少解釋執行的開銷,提高程序運行速度。

代碼緩存

代碼緩存(Code Cache)是Java虛擬機(JVM)中用於存儲已編譯代碼的特定區域。在JIT(Just-In-Time)編譯器將Java字節碼編譯成本地機器代碼時,這些生成的本地機器代碼被存儲在代碼緩存中。

代碼緩存的作用是保存已經編譯過的代碼,以便在程序的後續執行中直接使用這些本地機器代碼,而無需重複地進行編譯。這樣可以提高程序的性能,因爲避免了重複的編譯過程,減少了解釋執行的開銷。

C1和C2編譯器區別

在Java早期階段,存在兩種類型的JIT(即時編譯)編譯器,分別是Client(客戶端)和Server(服務器)。根據所需的JIT編譯器類型,需要下載並安裝相應的JDK。例如,如果您正在構建桌面應用程序,則需要下載具有“客戶端”JIT編譯器的JDK;如果是構建服務器應用程序,則需要下載具有“服務器”JIT編譯器的JDK。

一旦應用程序啓動,客戶端JIT編譯器就會開始對代碼進行編譯。而服務器JIT編譯器則會觀察代碼執行相當長的一段時間。根據其獲取的執行知識,服務器JIT編譯器將開始進行JIT編譯。儘管服務器JIT編譯速度較慢,但生成的代碼將比客戶端JIT編譯器生成的代碼更優化,性能更出色。

然而,現代的JDK現在內置了客戶端和服務器JIT編譯器。這兩個編譯器都嘗試對應用程序代碼進行優化。在應用程序啓動階段,會使用客戶端JIT編譯器對代碼進行編譯。隨着程序執行知識的積累,隨後會採用服務器JIT編譯器對代碼進行編譯。這種方法在JVM中被稱爲分層編譯。

JDK開發人員通常將這兩種編譯器稱爲客戶端和服務器JIT編譯器,而內部則分別稱爲c1和c2編譯器。因此,客戶端JIT編譯器所使用的線程被稱爲C1編譯器線程,而服務器JIT編譯器所使用的線程被稱爲C2編譯器線程。

C1、C2編譯器線程

C1、C2 編譯器線程的默認數量根據運行應用程序的容器/設備上可用的 CPU 數量確定。下表總結了 C1、C2 編譯器線程的默認數量:

中央處理器 c1 線程 c2 線程
1 1 1
2 1 1
4 1 2
8 1 2
16 2 6
32 3 7
64 4 8
128 4 10

C1、C2 編譯器優化

當c1和c2編譯器線程消耗大量CPU時,以下是解決該問題的潛在解決方案:

什麼都不做

如果C2編譯器線程的CPU消耗只是間歇性地偏高而不是持續性的,並且這種情況並未對您的應用程序性能造成明顯影響,可以考慮暫時忽略該問題。在某些情況下,臨時的CPU高消耗可能是正常的,可能是因爲JIT編譯器正在進行優化或在應用程序啓動後初始編譯所致。

然而,如果這種間歇性高CPU消耗開始對應用程序的性能產生負面影響,或者頻繁發生,並且持續時間較長,那麼可能需要進一步調查和解決。此時,可以考慮採取一些步驟,例如監視JIT編譯器的行爲、分析編譯日誌、調整JVM參數或升級到更新的JVM版本,以尋找潛在的解決方案。

總體來說,僅當間歇性的C2編譯器線程高CPU消耗並未對應用程序的整體性能產生重大影響時,暫時忽略該問題可能是一個可行的做法。但如果情況變得更加頻繁或持續,可能需要更深入地調查和處理。

分層編譯

-XX:-TieredCompilation JVM參數傳遞給應用程序將禁用JIT(Just-In-Time)熱點編譯。這意味着代碼將不會根據執行頻率進行動態優化,從而可能降低CPU消耗。然而,需要注意的是,作爲副作用,您的應用程序性能可能會受到影響,因爲禁用了JIT編譯會導致代碼執行時不再進行實時優化。

此參數的使用是一種權衡:通過降低CPU消耗來解決高CPU消耗問題,但可能以犧牲應用程序性能爲代價。因此,在使用這個參數之前,需要仔細權衡,並在實際應用程序環境中進行測試,以確保最終結果不會對應用程序的整體性能產生不可接受的影響。

設置分層等級

當CPU峯值是由C2編譯器線程單獨引起時,你可以選擇單獨關閉C2編譯。通過傳遞 -XX:TieredStopAtLevel=3 參數,可以實現這一目的。此參數的作用是僅啓用C1編譯器,同時禁用C2編譯器。

這種方法可以降低CPU消耗,因爲禁用C2編譯器會使系統只使用較輕量級的C1編譯器,但需要注意的是,這可能會影響到應用程序的性能。

在使用此參數之前,建議進行詳盡測試,以確保對應用程序性能的影響在可接受範圍內。選擇禁用C2編譯器應慎重考慮,因爲可能會犧牲應用程序的性能優化能力。

編譯分爲四層:

編譯級別 描述
0 解釋代碼
1 簡單的c1編譯代碼
2 有限的c1編譯代碼
3 完整的c1編譯代碼
4 C2編譯代碼

打印編譯信息

-XX:+PrintCompilation 是一個非常有用的JVM參數。通過傳遞此參數給您的應用程序,JVM將會打印有關應用程序編譯過程的詳細信息,這可以幫助你更好地瞭解代碼的實際編譯情況。

打印編譯信息可以提供有關哪些方法被編譯、何時被編譯以及使用了哪種類型的編譯器(比如C1或C2)等方面的詳細信息。這對於調整和優化應用程序的性能非常有幫助,因爲您可以通過查看輸出信息來了解編譯器在何處花費時間,從而有針對性地進行優化。

但需要注意的是,輸出的信息可能會非常詳細和龐大,可能會對系統性能產生一定的影響。因此,在生產環境中使用此參數時要小心謹慎,並且最好在測試環境中進行嘗試和調整,以避免對實際應用程序的性能產生不必要的負面影響。

設置緩存區大小

Hotspot JIT編譯器在JVM內存中有一個代碼緩存區域,用於存儲它編譯和優化的代碼。默認情況下,代碼緩存區域的大小爲240MB。

可以通過將 -XX:ReservedCodeCacheSize=N 傳遞給程序來增加代碼緩存的大小。例如 -XX:ReservedCodeCacheSize=512m 進行指定。

增加代碼緩存的大小有助於提高JIT編譯器在其中存儲優化代碼的容量,從而有可能減少編譯器線程的CPU消耗。這種調整可以爲編譯器提供更多的空間,以存儲更多的編譯代碼,減少由於不斷重編譯代碼而導致的性能損失。

增加代碼緩存的大小也會佔用更多的內存資源。在進行此類調整時,請確保考慮到系統的內存限制以及其他應用程序或組件對內存的需求,以避免因爲過度分配內存而導致系統性能問題。最佳做法是進行適度的調整並在測試環境中進行驗證,以確保對應用程序性能的提升並沒有不必要地犧牲其他方面的系統資源。

設置編譯線程數

可以使用參數 -XX:CICompilerCount 來控制C2編譯器線程的數量。默認情況下,C2編譯器線程的數量由JVM根據CPU核心數量和其他因素自動確定。但有時可能會發現C2編譯器線程數量較少,尤其是在具有多個CPU處理器或內核的系統上。

通過捕獲線程轉儲並上傳到適當的工具(如診斷工具或性能監控工具),我們可以查看C2編譯器線程的實際數量。如果C2編譯器線程數過少,您可以嘗試使用 -XX:CICompilerCount=8 這樣的參數來手動增加C2編譯器線程的數量。

增加C2編譯器線程的數量可能有助於提高JIT編譯的併發性能,特別是在具有更多CPU核心的系統上。但請注意,過多的編譯器線程可能會導致資源競爭和性能下降。因此,在調整此參數之前,請務必進行仔細的測試和評估,以確保其對應用程序性能的實際影響是積極的,並且不會造成其他系統方面的負面影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章