Java服務GC參數調優案例

本文介紹了一次生產環境的JVM GC相關參數的調優過程,經過參數的調整避免了GC卡頓對JAVA服務成功率的影響html

背景以及遇到的問題

咱們的Java HTTP服務屬於OLTP類型,對成功率和響應時間的要求比較高,在生產環境中出現偶現的成功率忽然降低而後又自動恢復的狀況,如圖所示:java

圖片描述

JVM和GC相關的參數以下:nginx

-Xmx22528m

-Xms22528m

-XX:NewRatio=2

-XX:+UseConcMarkSweepGC

-XX:+UseParNewGC

-XX:+CMSParallelRemarkEnabled

總結來講,因爲服務中大量使用了Cache,因此堆大小開到了22G。GC算法使用CMS(UseConcMarkSweepGC),開啓了下降標記停頓(CMSParallelRemarkEnabled),設置年輕代爲並行收集(UseParNewGC),年輕代和老年代的比例爲1:2 (NewRatio=2).算法

JVM GC日誌相關的參數以下:服務器

-Xloggc:/data/gc.log

-XX:GCLogFileSize=10M

-XX:NumberOfGCLogFiles=10

-XX:+UseGCLogFileRotation

-XX:+PrintGCDateStamps

-XX:+PrintGCTimeStamps

-XX:+PrintGCDetails

-XX:+DisableExplicitGC

-verbose:gc

問題解決過程

排除應用程序的內存使用問題

首先使用jmap查看內存使用狀況:併發

jmap -histo:live PID

這個命令把程序中當前的對象按照個數和佔用的空間排序之後打印出來。這裏沒有發現使用異常的對象。oracle

排除Cache內容過多的問題

若是Cache內容過多也會致使JVM老年代容易被用滿致使頻繁GC,所以調出GC日誌進行查看,發現每次GC之後內存使用通常是從20G下降到5G左右,所以常駐內存的Cache不是致使GC長時間卡頓的根本緣由。對於GC LOG的查看有多種方式,使用VisualVM比較直觀,須要安裝VisualGC插件:spa

圖片描述

從圖中咱們能夠看到伊甸園和老年代的空間分配,因爲總體內存是20G,設置 -XX:NewRatio=2 所以老年代是14G,伊甸園+S0+S1=7G.net

調整GC時間點(成功率抖動問題加劇)

若是GC須要處理的內存量比較大,執行的時間也就比較長,STW (Stop the World)時間也就更長。按照這個思路調整CMS啓動的時間點,但願提前GC,也就是讓GC變得更加頻繁可是指望每次執行的時間較少。添加了下面這兩個參數:插件

-XX:+UseCMSInitiatingOccupancyOnly

-XX:CMSInitiatingOccupancyFraction=50

意思是說在Old區使用了50%的時候觸發GC。實驗後發現GC的頻率有所增長,可是每次GC形成的陳功率下降現象並無減弱,所以棄用這兩個參數。

調整對象在年輕代內存中駐留的時間(效果不明顯)

若是可以下降老年代GC的頻率也能夠達到下降GC影響的目的,所以嘗試讓對象在年輕代內存中進行更長時間的駐留,提高這些對象在年輕代GC時候被銷燬的機率。使用參數 -XX:MaxTenuringThreshold=31調整之後收效不明顯。

CMS-Remark以前強制進行年輕代的GC

首先補充一下CMS的相關知識,在CMS整個過程當中有兩個步驟是STW的,如圖紅色部分:

圖片描述

CMS並不是沒有暫停,而是用兩次短暫停來替代串行標記整理算法的長暫停,它的收集週期是這樣:

  1. 初始標記(CMS-initial-mark),從root對象開始標記存活的對象

  2. 併發標記(CMS-concurrent-mark)

  3. 從新標記(CMS-remark),暫停全部應用程序線程,從新標記併發標記階段遺漏的對象(在併發標記階段結束後對象狀態的更新致使)

  4. 併發清除(CMS-concurrent-sweep)

  5. 併發重設狀態等待下次CMS的觸發(CMS-concurrent-reset)。

經過GC日誌和成功率降低的時間點進行比對發現並非每一次老年代GC都會致使成功率的降低,可是從中發現了一個規律:

圖片描述

前兩次GC CMS-Remark過程在4s左右形成了成功率的降低,可是第三次GC並無對成功率形成明顯的影響,CMS-Remark只有0.18s。Java HTTP 服務是經過Nginx進行反向代理的,nginx設置的超時時間是3s,因此若是GC卡頓在3s之內就不會對成功率形成太大的影響。

從GC日誌中又發現一個信息:

圖片描述

在文檔和相關資料中沒有找到藍色部分的含義,猜想是remark處理的內存量,處理的越多就越慢。添加下面兩個參數強制在remark階段和FULL GC階段以前先在進行一次年輕代的GC,這樣須要進行處理的內存量就不會太多。

-XX:+ScavengeBeforeFullGC 

-XX:+CMSScavengeBeforeRemark

調優之後效果很明顯,下面是兩臺配置徹底相同的服務器在同一時間段的成功率和響應時間監控圖,第一個沒有添增強制年輕代GC的參數。

圖片描述
圖片描述

結論

  1. 在CMS-remark階段須要對堆中全部的內存對象進行處理,若是在這個階段以前強制執行一次年輕代的GC會大量減小remark須要處理的內存數量,進而下降JVM卡頓對成功率的影響

  2. 對於Java HTTP服務,JVM的卡頓時間應該小於HTTP客戶端的調用超時時間,不然JVM卡頓會對成功率形成影響

參考文獻

  1. http://blog.csdn.net/historyasamirror/article/details/6245157

  2. https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs

  3. http://blog.sokolenko.me/2014/11/javavm-options-production.html

相關文章
相關標籤/搜索