捅破JVM調優這層薄薄的窗戶紙

說起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)

  • 具體排查常用的一些命令:

    1. top命令 可看到具體的進程佔用CPU、內存的情況
    2. top -Hp 進程號 觀察進程中的線程,哪個線程CPU和內存佔比高
    3. jstack 線程號 定位線程的具體情況
    4. 也可用jsp命令找出所有的java進程,然後再用jstack定位具體的線程
    5. jstat -gc 動態觀察gc情況
    6. jstat -gc 進程號 500 : 每隔500個毫秒打印GC的情況
    7. jmap - histo 進程號 查看對象創建信息,有多少對象創建
    8. jmap -dump:format=b,file=xxx 進程號 將對象信息轉儲,轉儲時耗費資源,會對系統造成影響
    9. 使用MAT / jhat /jvisualvm 進行dump文件分析 jhat -J-mx512M xxx.dump jhat使用
    10. 通過排查最終定位代碼

    注: 線上系統,內存特別大,jmap執行期間會對進程產生很大影響,甚至卡頓(電商不適合)可設定了參數HeapDump,OOM的時候會自動產生堆轉儲文件 -XX:+HeapDumpOnOutOfMemoryError

  • 一個JDK自帶的圖形化工具jvisualvm

  • arthas在線排查工具

  1. 在生產上我們經常會碰到一些不好排查的問題,例如線程安全問題,用最heapdump不好查到問題原因。爲了排查這些問題,有時我們會臨時加一些日誌,比如在一些關鍵的函數裏打印出入參,然後重新打包發佈,如果打了日誌還是沒找到問題,繼續加日誌,重新打包發佈。對於上線流程複雜而且審覈比較嚴的公司,從改代碼到上線需要層層的流轉,會大大影響問題排查的進度。
  2. 步驟:1)啓動arthas java -jar arthas-boot.jar 2 )綁定java進程 3)ashboard命令觀察系統整體情況 4)help 查看幫助 5 )help xx 查看具體命令幫助
  3. jvm 觀察jvm信息
  4. thread 定位線程問題
  5. dashboard 觀察系統情況
  6. heapdump + jhat分析
  7. jad反編譯 動態代理生成類的問題定位 第三方的類(觀察代碼) 版本問題(確定自己最新提交的版本是不是被使用)
  8. redefine 熱替換 目前有些限制條件:只能改方法實現(方法已經運行完成),不能改方法名, 不能改屬性
  9. 沒有包含的功能:jmap
  10. arthas使用幫助

JVM常用的命令行參數

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日誌詳解

  1. PS\PO日誌格式:
    在這裏插入圖片描述
  2. heap dump信息
    在這裏插入圖片描述
    eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
    後面的內存地址指的是,起始地址,使用空間結束地址,整體空間結束地址
  3. 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做準備
  1. 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飆高,但內存回收特別少)

  1. 硬件升級系統反而卡頓的問題
    當內存變大時,垃圾回收器沒有改變,可能會使得STW時間邊長

  2. 線程池不當運用產生OOM問題

  3. 系統老是卡頓,但不知問題所在,需要不間斷重啓才能使用
    採用 加內存 + 更換垃圾回收器 G1後解決問題 真正問題在哪兒 ?不知道

  4. tomcat中 http-header-size設置過大,造成OOM,http-header-size設置過大,會使得Http11OutputBuffer這個對象過大,造成OOM 參考

  5. lambda表達式導致方法區溢出問題
    當方法區設置過小,而不斷的有lambda表達式產生時,會造成方法區OOM; 在使用lambda表達式時會有內部類產生。

  6. 棧溢出問題 -Xss設定太小

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