C2 CompilerThread9 長時間佔用CPU解決方案

一、描述:異常線程的堆棧如下:

"C2 CompilerThread9" #48 daemon prio=9 os_prio=0 tid=0x00007f45f0b80000 nid=0x188 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

補充描述:我的應用類型爲後臺接口服務,系統秒級調用峯值在10W+,JRE版本如下:

java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)

二、問題解決過程:關閉JIT分層編譯

步驟一:瞭解JIT編譯原理

因爲之前對JIT的編譯原理並不瞭解,不敢隨意修改線上服務器的編譯類型,擔心會有一些其他的副作用,所以在網上開始了查閱資料學習的過程。

什麼是JIT編譯?
編譯器在編譯過程中通常會考慮很多因素。比如:彙編指令的順序。假設我們要將兩個寄存器的值進行相加,執行這個操作一般只需要一個CPU週期;但是在相加之前需要將數據從內存讀到寄存器中,這個操作是需要多個CPU週期的。編譯器一般可以做到,先啓動數據加載操作,然後執行其它指令,等數據加載完成後,再執行相加操作。由於解釋器在解釋執行的過程中,每次只能看到一行代碼,所以很難生成上述這樣的高效指令序列。而編譯器可以事先看到所有代碼,因此,一般來說,解釋性代碼比編譯性代碼要慢。

java 作爲靜態語言十分特殊,他需要編譯,但並不是在執行之前就編譯爲本地機器碼。Java的實現在解釋性和編譯性之間進行了折中,Java代碼是編譯性的,它會被編譯成一個平臺獨立的字節碼程序。JVM負責加載、解釋、執行這些字節碼程序,在這個過程中,還可能會將這些字節碼實時編譯成目標機器碼,以便提升性能。

所以,在談到 java的編譯機制的時候,其實應該按時期,分爲兩個部分。一個是 javac指令 將java源碼變爲 java字節碼的靜態編譯過程。 另一個是 java字節碼編譯爲本地機器碼的過程,並且因爲這個過程是在程序運行時期完成的所以稱之爲即時編譯(JIT:Just In Time)。

JIT編譯類型:C1編譯器、C2編譯器、分層編譯器

通常我們說即時編譯器有兩種類型,Client Compiler(C1編譯器)和Server Compiler(C2編譯器)。這兩種編譯器最大的區別就是,編譯代碼的時間點不一樣。C1編譯器會更早的對代碼進行編譯,因此在程序剛啓動的時候,C1編譯器比C2編譯器執行的更快,所以C1編譯器適用於一些GUI應用,可以縮短應用啓動時間。C2編譯器會收集更多的信息,然後纔對代碼進行編譯優化,所以從長遠角度考慮,C2編譯器最終可以產生比C1編譯器更優秀的代碼,適用於長時間運行的後臺接口服務。

可能大家都有一個困擾,JVM爲什麼要將編譯器分爲client和server,爲什麼不在程序啓動時,使用client編譯器,在程序運行一段時間後,自動切換爲server編譯器? 其實,這種技術是存在的,一般稱之爲 Tiered Compiler(分層編譯器)。Java7 和Java 8可以使用選項-XX:+TieredCompilation來打開(-server選項也要打開)。在Java 8中,-XX:+TieredCompilation默認是打開的。

分層編譯將 JVM 的執行狀態分爲了 5 個層次:

  • 第 0 層:程序解釋執行,默認開啓性能監控功能(Profiling),如果不開啓,可觸發第二層編譯;
  • 第 1 層:可稱爲 C1 編譯,將字節碼編譯爲本地代碼,進行簡單、可靠的優化,不開啓 Profiling;
  • 第 2 層:也稱爲 C1 編譯,開啓 Profiling,僅執行帶方法調用次數和循環回邊執行次數 profiling 的 C1 編譯;
  • 第 3 層:也稱爲 C1 編譯,執行所有帶 Profiling 的 C1 編譯;
  • 第 4 層:可稱爲 C2 編譯,也是將字節碼編譯爲本地代碼,但是會啓用一些編譯耗時較長的優化,甚至會根據性能監控信息進行一些不可靠的激進優化。

在一些特殊情況下,激進優化後的代碼並不能有更高的性能。需要進行優化回退,將重新對代碼進行解釋執行。因此

C2編譯器相對於C1編譯器更適用於我們系統
分層編譯器是綜合考慮C1和C2編譯器的優點衍生出的一種進化版本編譯器,但是由於我們是純後臺應用,這種衍生優化是否有效未可知。
分層編譯器在一些特殊情況下可能比較激進、不可靠。

JIT學習參考博文:
https://blog.csdn.net/qq_28674045/article/details/51896129
https://www.cnblogs.com/insistence/p/5901457.html
https://www.cnblogs.com/death00/p/11722130.html

步驟二:關閉分層編譯,啓用C2編譯器
JVM啓動腳本中添加如下參數

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