說起JVM調優,大多數人都聞風喪膽,但其實它並沒有那麼難,你與JVM調優的距離僅差一層窗戶紙,今天讓我來幫你們將它捅破。
調優是什麼
調優是爲了追求吞吐量或響應時間而做的一系列硬件、軟件的優化活動。簡單理解爲了提升吞吐量或相應時間,結合具體的業務場景我們應該使用多大內存、什麼樣的CPU等硬件和給JVM設置什麼樣的參數使得JVM的垃圾回收時間縮短。
調優是爲了什麼
在談到調優時,我們應該明確,調優是爲了追求什麼?其實很簡單,調優有兩個指標,第一,吞吐量;第二,響應時間。我們所做的調優工作就是圍繞這兩個根本性的目的而進行,不同的項目所追求的目標不同,這兩個目標不可能同時達到極致。
- 吞吐量:用戶代碼執行時間/(用戶代碼執行時間+垃圾回收時間)
- 響應時間:垃圾回收時STW時間越短,相應時間越短
調優怎麼做
我們可將調優分爲3個階段,分別進行調優
1. 前期規劃
結合具體的業務,計算需要的機器內存大小、CPU,選擇合適的垃圾回收器(組合)、設置合適的各年齡代內存大小、升級年齡,設置好GC日誌參數。
注: 響應時間:CMS 吞吐量:PS
2. 優化運行環境(卡、慢)
結合業務過程和出現的問題,藉助一些工具找到原因所在,根據具體的問題再進行優化,可擴大內存、更換垃圾回收器、調節JVM參數。
-
案例參考: 有一個50萬PV的資料類網站(從磁盤提取文檔到內存)原服務器32位,1.5G 的堆,用戶反饋網站比較緩慢,因此公司決定升級,新的服務器爲64位,16G 的堆內存,結果用戶反饋卡頓十分嚴重,反而比以前效率更低了
爲什麼原網站慢?
很多用戶瀏覽數據,很多數據load到內存,內存不足,頻繁GC,STW長,響應時間變慢
爲什麼會更卡頓?
內存越大,FGC時間越長
咋辦?
PS -> PN + CMS 或者 G1 -
系統CPU經常100%,如何調優?(面試高頻) CPU100%那麼一定有線程在佔用系統資源,
找出哪個進程cpu高(top)
該進程中的哪個線程cpu高(top -Hp)
導出該線程的堆棧 (jstack)
查找哪個方法(棧幀)消耗時間 (jstack)
工作線程佔比高(是否死鎖,死循環) | 垃圾回收線程佔比高(爲什麼頻繁GC,哪類對象有問題還是內存不足) -
系統內存飆高,如何查找問題?(面試高頻)
導出堆內存 (jmap)
分析 (jhat jvisualvm mat … ),看哪類對象創建的多,而且不能被回收 -
如何監控JVM
jstat jvisualvm jprofiler arthas top…
3. 解決運行中出現的問題
一般是運維團隊首先收到報警信息(CPU Memory)
-
具體排查常用的一些命令:
- top命令 可看到具體的進程佔用CPU、內存的情況
- top -Hp 進程號 觀察進程中的線程,哪個線程CPU和內存佔比高
- jstack 線程號 定位線程的具體情況
- 也可用jsp命令找出所有的java進程,然後再用jstack定位具體的線程
- jstat -gc 動態觀察gc情況
- jstat -gc 進程號 500 : 每隔500個毫秒打印GC的情況
- jmap - histo 進程號 查看對象創建信息,有多少對象創建
- jmap -dump:format=b,file=xxx 進程號 將對象信息轉儲,轉儲時耗費資源,會對系統造成影響
- 使用MAT / jhat /jvisualvm 進行dump文件分析 jhat -J-mx512M xxx.dump jhat使用
- 通過排查最終定位代碼
注: 線上系統,內存特別大,jmap執行期間會對進程產生很大影響,甚至卡頓(電商不適合)可設定了參數HeapDump,OOM的時候會自動產生堆轉儲文件 -XX:+HeapDumpOnOutOfMemoryError
-
一個JDK自帶的圖形化工具jvisualvm
-
arthas在線排查工具
- 在生產上我們經常會碰到一些不好排查的問題,例如線程安全問題,用最heapdump不好查到問題原因。爲了排查這些問題,有時我們會臨時加一些日誌,比如在一些關鍵的函數裏打印出入參,然後重新打包發佈,如果打了日誌還是沒找到問題,繼續加日誌,重新打包發佈。對於上線流程複雜而且審覈比較嚴的公司,從改代碼到上線需要層層的流轉,會大大影響問題排查的進度。
- 步驟:1)啓動arthas java -jar arthas-boot.jar 2 )綁定java進程 3)ashboard命令觀察系統整體情況 4)help 查看幫助 5 )help xx 查看具體命令幫助
- jvm 觀察jvm信息
- thread 定位線程問題
- dashboard 觀察系統情況
- heapdump + jhat分析
- jad反編譯 動態代理生成類的問題定位 第三方的類(觀察代碼) 版本問題(確定自己最新提交的版本是不是被使用)
- redefine 熱替換 目前有些限制條件:只能改方法實現(方法已經運行完成),不能改方法名, 不能改屬性
- 沒有包含的功能:jmap
- arthas使用幫助
JVM常用的命令行參數
HotSpot參數分類
標準: - 開頭,所有的HotSpot都支持
非標準:-X 開頭,特定版本HotSpot支持特定命令
不穩定:-XX 開頭,下個版本可能取消
GC常用參數
-Xmn -Xms -Xmx -Xss 年輕代 最小堆 最大堆 棧空間
-XX:+UseTLAB 使用TLAB,默認打開
-XX:+PrintTLAB 打印TLAB的使用情況
-XX:TLABSize 設置TLAB大小
-XX:+DisableExplictGC System.gc()不管用 ,FGC
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationConcurrentTime 打印應用程序時間
-XX:+PrintGCApplicationStoppedTime) 打印暫停時長
-XX:+PrintReferenceGC 記錄回收了多少種不同引用類型的引用
-verbose:class 類加載詳細過程
-XX:+PrintVMOptions
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必須會用
-Xloggc:opt/log/gc.log
-XX:MaxTenuringThreshold 升代年齡,最大值15
Parallel常用參數
-XX:SurvivorRatio
-XX:PreTenureSizeThreshold 大對象到底多大
-XX:MaxTenuringThreshold
-XX:+ParallelGCThreads 並行收集器的線程數,同樣適用於CMS,一般設爲和CPU核數相同
-XX:+UseAdaptiveSizePolicy 自動選擇各區大小比例
CMS常用參數
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads CMS線程數量
-XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代後開始CMS收集,默認是68%(近似值),如果頻繁發生 SerialOld卡頓,應該調小,(頻繁CMS回收)
-XX:+UseCMSCompactAtFullCollection 在FGC時進行壓縮
-XX:CMSFullGCsBeforeCompaction 多少次FGC之後進行壓縮
-XX:+CMSClassUnloadingEnabled
-XX:CMSInitiatingPermOccupancyFraction 達到什麼比例時進行Perm回收
GCTimeRatio 設置GC時間佔用程序運行時間的百分比
-XX:MaxGCPauseMillis 停頓時間,是一個建議時間,GC會嘗試用各種手段達到這個時間,比如減小年輕代
G1常用參數
-XX:+UseG1GC
-XX:MaxGCPauseMillis 建議值,G1會嘗試調整Young區的塊數來達到這個值
-XX:GCPauseIntervalMillis ?GC的間隔時間
-XX:+G1HeapRegionSize 分區大小,建議逐漸增大該值,1 2 4 8 16 32。 隨着size增加,垃圾的存活時間更長,GC間隔更長,但每次GC的時間也會更長 ZGC做了改進(動態區塊大小)
G1NewSizePercent 新生代最小比例,默認爲5%
G1MaxNewSizePercent 新生代最大比例,默認爲60%
GCTimeRatio GC時間建議比例,G1會根據這個值調整堆空間
ConcGCThreads 線程數量
InitiatingHeapOccupancyPercent 啓動G1的堆空間佔用比例
常用的垃圾回收器組合參數
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序。默認情況下不會是這種選項,HotSpot會根據計算及配置和JDK版本自動選擇收集器
-XX:+UseParNewGC = ParNew + SerialOld
這個組合已經很少用(在某些版本中已經廢棄)
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默認) 【PS + SerialOld】
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
GC日誌詳解
- PS\PO日誌格式:
- heap dump信息
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
後面的內存地址指的是,起始地址,使用空間結束地址,整體空間結束地址 - CMS日誌
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
//8511 (13696) : 老年代使用(最大)
//9866 (19840) : 整個堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
//這裏的時間意義不大,因爲是併發執行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//標記Card爲Dirty,也稱爲Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//STW階段,YG occupancy:年輕代佔用及容量
//[Rescan (parallel):STW下的存活對象標記
//weak refs processing: 弱引用處理
//class unloading: 卸載用不到的class
//scrub symbol(string) table:
//cleaning up symbol and string tables which hold class-level metadata and
//internalized string respectively
//CMS-remark: 8511K(13696K): 階段過後的老年代佔用及容量
//10108K(19840K): 階段過後的堆佔用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
//標記已經完成,進行併發清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//重置內部結構,爲下次GC做準備
- G1日誌
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年輕代 Evacuation-> 複製存活對象
//initial-mark 混合回收的階段,這裏是YGC混合老年代回收
[Parallel Time: 1.5 ms, GC Workers: 1] //一個GC線程
[GC Worker Start (ms): 92635.7]
[Ext Root Scanning (ms): 1.1]
[Update RS (ms): 0.0]
[Processed Buffers: 1]
[Scan RS (ms): 0.0]
[Code Root Scanning (ms): 0.0]
[Object Copy (ms): 0.1]
[Termination (ms): 0.0]
[Termination Attempts: 1]
[GC Worker Other (ms): 0.0]
[GC Worker Total (ms): 1.2]
[GC Worker End (ms): 92636.9]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.0 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
//以下是混合回收其他階段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//無法evacuation,進行FGC
[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
調優的一些案例
OOM產生的原因多種多樣,有些程序未必產生OOM,不斷FGC(CPU飆高,但內存回收特別少)
-
硬件升級系統反而卡頓的問題
當內存變大時,垃圾回收器沒有改變,可能會使得STW時間邊長 -
線程池不當運用產生OOM問題
-
系統老是卡頓,但不知問題所在,需要不間斷重啓才能使用
採用 加內存 + 更換垃圾回收器 G1後解決問題 真正問題在哪兒 ?不知道 -
tomcat中 http-header-size設置過大,造成OOM,http-header-size設置過大,會使得Http11OutputBuffer這個對象過大,造成OOM 參考
-
lambda表達式導致方法區溢出問題
當方法區設置過小,而不斷的有lambda表達式產生時,會造成方法區OOM; 在使用lambda表達式時會有內部類產生。 -
棧溢出問題 -Xss設定太小