Young GC 500ms到50ms的優化

問題描述:在進行雙十一備戰的過程中,發現JVMyoung GC的頻次很高,同時一次Young Gc的耗時在500ms左右,FullGC的在1-2天觸發一次

JVM原配置:-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCMSInitiatingOccupancyOnly  -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

優化方向:關注Young GC ,FullGC觸發次數可不考慮

(1)、考慮到這個應用是一個WEB應用,同時也是MQ的消費端,應該第一個原因就是MQ的消費會導致大量的臨時對象產生,YOUNG GC頻繁,同時在接受商品上下架(全站)、收銀臺對賬消息,每個都在3000/TPS的量,峯值能達到10000+。所以第一個優化思路,是將MQ消費應用,與WEB應用分離,減少之間的影響。

優化完,MQ消費應用YOUNGGC頻次同之前,WEB應用YOUNGGC2分鐘一次,每次GC耗時:50ms

總結:資源隔離,保護線上稀有資源,是針對系統的優化需要關注的點,此時針對於YOUNG GC已到達本身的系統要求,針對於MQ消費者應用,YOUNGGC頻次高一些,對於消費應用的延遲來說還是可接受的。但是還是想搞清楚時間消耗在哪裏?

(2)YoungGC時間消耗:

   1.Root Scanning        

   2.Object Copy  

GC roots 包含以下引用:                         

    1.所有java線程以及線程棧幀裏指向GC堆裏的對象的引用;                            

    2.JNI Local & Global;                                                                                           

    3.由系統類加載器(system class loader)加載的對象,這些類是不能夠被回收的;      

    4.stack local Java方法的local變量或參數;                                              

    5.其他,包含monitor & finalizable & native stack

排查方法,打印各種日誌。

-XX:+PrintGCDetails -Xloggc:/export/Logs/gc.log 打印日誌,,

XX:+PrintReferenceGC 打印Reference回收消耗時長

-XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 打印安全點時間消耗

-XX:+PrintTenuringDistributio 打印年齡分佈(注意動態年齡計算)

查看日誌後發現,這幾個的時間跟500ms都不是一個量級的,所以根本原因不在這裏。

後面考慮到沒有設置GC回收線程數量,通過DUMP、JCMD命令可以發現GC線程數量遠遠超過服務器核數,(4C8G服務器配置)

    ①如果用戶顯示指定了ParallelGCThreads,則使用用戶指定的值。
    ②否則,需要根據實際的CPU所能夠支持的線程數來計算ParallelGCThreads的值,計算方法見步驟③和步驟④。
    ③如果物理CPU所能夠支持線程數小於8,則ParallelGCThreads的值爲CPU所支持的線程數。這裏的閥值爲8,是因爲JVM中調用nof_parallel_worker_threads接口所傳入的switch_pt的值均爲8。
    ④如果物理CPU所能夠支持線程數大於8,則ParallelGCThreads的值爲8加上一個調整值,調整值的計算方式爲:物理CPU所支持的線程數減去8所得值的5/8或者5/16,JVM會根據實際的情況來選擇具體是乘以5/8還是5/16。
    比如,在64線程的x86 CPU上,如果用戶未指定ParallelGCThreads的值,則默認的計算方式爲:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。

  ParallelGCThreads= 8 + (N - 8) * 5 / 8

手動設置,ParallelGCThreads= 4

YoungGC耗時變成75ms左右。

此時大部分GC耗時已經解決,繼續優化,

1.發現安全點時間耗時都是幾ms,故排除這個優化點

2.finalReference耗時在 20-40ms左右,開啓-XX:+ParallelRefProcEnabled,並行回收,優化到60ms左右

3.打印年齡代分佈,可以看到age1  age2差距比較大,應該大多在第一次younggc就可以回收的對象,在eden區又copy到 s0區,減少,copy,可以稍微增加xmn設置大小,(測試了1G 1.5G 2G)根據不同系統可能不同,最終選擇2G在頻次和耗時之間折中選擇。

備註:

AGE年齡優化分析:

Eden移動到Survivor情況:假設機器2 Eden區域是 機器1 的兩倍大,其他條件都保持不變。就一般情況來說(Survivor區域中存活的對象比Eden少很多,比如1%),那麼機器1 young gc的頻率是 機器2 young gc 頻率的2倍;那麼假設機器1在T時間內GC一次,在GC之後由Eden區域晉升到Survivor的大小爲10M(即age=1),那麼機器2在2T時間之後發生GC,1T-2T之間生成的對象和機器1類似,GC之後有10M進入Survivor區域,但是0T-1T內最多會剩餘10M內存可能會進入到Survivor,但是在經歷1T-2T時間之後也有可能導致object已經不存活,如何判斷這部分對象有沒有存活呢,在機器1在2T的時間點要又要進行一次young gc,那麼在0T-1T之前存活的對象也就是age=1的對象將會再次會經歷一次young gc,便是了age=2,所有看age=2的年齡段剩餘多少就可以了。機器2一次GC之後,由Eden區域進入到Survivor區域中的大約等於10M+機器1中Survivor中(age=2)也就是機器1:age1+age2中的object對象。

-Xmn2048M -XX:ParallelGCThreads=4  -XX:+ParallelRefProcEnabled  -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=15 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/export/Logs/gc.log -XX:+PrintReferenceGC -XX:+PrintTenuringDistribution -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

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