被虐后,分享JVM調優原理相關的知識和經驗
本文只介紹一些原理和思路的內容,希望對你與所幫助!
先了解原理,然后在進行調優。
一定要記住的是任何的調優都不是一蹴而就,不要指望改動一個參數就達到調優的目的,也不要僅僅改動了一個參數,就認為是做了調優。調優是在已有的資源和要達到的目標的前提上,進行權衡。
從類加載到整個JVM的運行周期內大致流程和結構如下:
從上圖可以看到,JVM 可以劃分為這些部分:
執行引擎,包括:GC、JIT 編譯器
類加載子系統,這部分的問題,一般在開發過程中出現
JNI 部分,這部分問題一般在 JVM 之外
運行時數據區;Java 將內存分為 2 大塊:堆內存和棧內存
首先我們要對上述的內容有一定的了解,從全局出發。看了上圖,在調優中我們能做的也就是對運行時數據區進行一些操作,然后選擇執行引擎用何種垃圾收集器對垃圾進行回收。
1、本文調優思路只針對JVM1.8,先看下JVM1.8內存模型
注意: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;
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兩種收集器的優點,又摒棄了其缺陷, 非獨占式
收集器的好壞,主要有兩個指標:停頓時間和吞吐量
GC 選擇的經驗總結
綜合來看,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。
對于內存大小的考量:
- 一般 4G 以上,算是比較大,用 G1 的性價比較高。
- 一般超過 8G,比如 16G-64G 內存,非常推薦使用 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相關問題,請記住十六字箴言:做好監控,定位問題,驗證結果,總結歸納。