[轉帖]基礎篇:JVM調優原理相關的知識和經驗分享

济南小老虎發表於2024-05-31
https://heapdump.cn/monographic/detail/31/4285541

本文只介紹一些原理和思路的內容,希望對你與所幫助!‍

  • 先了解原理,然後在進行調優。
    一定要記住的是任何的調優都不是一蹴而就,不要指望改動一個引數就達到調優的目的,也不要僅僅改動了一個引數,就認為是做了調優。調優是在已有的資源和要達到的目標的前提上,進行權衡。

    從類載入到整個JVM的執行週期內大致流程和結構如下:
    image.png

從上圖可以看到,JVM 可以劃分為這些部分:

執行引擎,包括:GC、JIT 編譯器

類載入子系統,這部分的問題,一般在開發過程中出現

JNI 部分,這部分問題一般在 JVM 之外

執行時資料區;Java 將記憶體分為 2 大塊:堆記憶體和棧記憶體

首先我們要對上述的內容有一定的瞭解,從全域性出發。看了上圖,在調優中我們能做的也就是對執行時資料區進行一些操作,然後選擇執行引擎用何種垃圾收集器對垃圾進行回收。

1、本文調優思路只針對JVM1.8,先看下JVM1.8記憶體模型

image.png

image.png

注意:JVM 執行緒佔用的是系統空間,所以當JVM的堆記憶體越大,系統本身的記憶體就越少,自然可生成的執行緒數量就越少。

2、JVM調優,主要從兩個方面考慮:堆記憶體大小配置和垃圾回收演算法選擇

# 設定堆記憶體
-Xmx4g -Xms4g 
# 指定 GC 演算法
-XX:+UseG1GC -XX:MaxGCPauseMillis=50 
# 指定 GC 並行執行緒數
-XX:ParallelGCThreads=4 
# 列印 GC 日誌
-XX:+PrintGCDetails -XX:+PrintGCDateStamps 
# 指定 GC 日誌檔案
-Xloggc:gc.log 
# 指定 Meta 區的最大值
-XX:MaxMetaspaceSize=2g 
# 設定單個執行緒棧的大小
-Xss1m 
# 指定堆記憶體溢位時自動進行 Dump
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/usr/local

3、JVM - GC型別組合以及適用場合

垃圾回收,分為Young區垃圾回收器,以及Old區垃圾回收器兩部分,兩部分需要組合使用:

新生代收集器:Serial、ParNew、Parallel Scavenge;

老年代收集器:Serial Old、Parallel Old、CMS;
image.png

serial:新生代收集器,是最早出現最成熟的收集器,單執行緒,獨佔式,GC時會stop the world 應用暫停。

ParNew :新生代收集器,是Serial 收集器的多執行緒版本,獨佔式,同樣地GC時會stop the world 應用暫停。

Parallel Scavenge:新生代收集器, 獨佔式, 與ParNew相似,特點:吞吐量優先

CMS:(concurrent-mark-sweep),老年代收集器,非獨佔式,多個執行緒,標記並清除演算法,響應時間優先,缺點是GC後不進行記憶體整理,會有記憶體碎片。

Serial Old:是Serial 的老年代版本,同樣是單執行緒收集器, 獨佔式

Parallel Old 是Parallel Scavenge 收集器的老年代版本,多執行緒收集, 獨佔式

G1:備受期待的新一代垃圾收集器,可預測的停頓:這又是G1相對於CMS的一大優勢,結合了Parallel Scavenge以及CMS兩種收集器的優點,又摒棄了其缺陷, 非獨佔式

image.png
收集器的好壞,主要有兩個指標:停頓時間和吞吐量

GC 選擇的經驗總結

image.png

綜合來看,G1 是 JDK11 之前 HotSpot JVM 中最先進的準產品級(production-ready) 垃圾收集器。重要的是,HotSpot 工程師的主要精力都放在不斷改進 G1 上面。在更新的 JDK 版本中,將會帶來更多強大的功能和最佳化。

可以看到,G1 作為 CMS 的代替者出現,解決了 CMS 中的各種疑難問題,包括暫停時間的可預測性,並終結了堆記憶體的碎片化。對單業務延遲非常敏感的系統來說,如果 CPU 資源不受限制,那麼 G1 可以說是 HotSpot 中最好的選擇,特別是在最新版本的 JVM 中。當然這種降低延遲的最佳化也不是沒有代價的:由於額外的寫屏障和守護執行緒,G1 的開銷會更大。如果系統屬於吞吐量優先型的,又或者 CPU 持續佔用 100%,而又不在乎單次 GC 的暫停時間,那麼 CMS 是更好的選擇。

  • 總之,G1 適合大記憶體,需要較低延遲的場景。

選擇正確的 GC 演算法,唯一可行的方式就是去嘗試,並找出不合理的地方,一般性的指導原則:

  • 如果系統考慮吞吐優先,CPU 資源都用來最大程度處理業務,用 Parallel GC;
  • 如果系統考慮低延遲有限,每次 GC 時間儘量短,用 CMS GC;
  • 如果系統記憶體堆較大,同時希望整體來看平均 GC 時間可控,使用 G1 GC。

最後討論一個很多開發者經常忽視的問題,也是面試大廠常問的問題:

  • JDK 8 的預設 GC 是什麼?

很多人或覺得是 CMS,甚至 G1,其實都不是。

答案是:JDK 8並行 GC 是 JDK8 裡的預設 GC 策略。預設使用的是 Parallel Scavenge (新生代) 和 Parallel Old (老年代),基於我的Jdk 1.8.0_181-b13版本

注意,G1 成為 JDK9 以後版本的預設 GC 策略,同時,ParNew + SerialOld 這種組合不被支援。

最後在分享一下,調優後如果還遇到JVM相關問題,請記住十六字箴言:做好監控,定位問題,驗證結果,總結歸納。

相關文章