天天看點

(轉)關于施用full gc頻繁的分析及解決

分析

當頻繁full gc時,jstack列印出堆棧資訊如下:
           
可以看到的确是在跑低價資訊
           
另外在應用頻繁full gc時和應用正常時,也執行了如下2種指令:
           
目的是确認以下2種資訊:
           
(1)是否存在某些引用的不正常,造成對象始終可達而無法回收(Java中的記憶體洩漏)
           
(2)是否真是由于在頻繁full gc時同時又有大量請求進入配置設定記憶體進而處理不過來,
        造成concurrent mode failure?
           
下圖是在應用正常情況下,jmap不加live,産生的histo資訊:
           
下圖是在應用正常情況下,jmap加live,産生的histo資訊:
           
下圖是在應用頻繁full gc情況下,jmap不加live和加live,産生的histo資訊:
           
從上述幾個圖中可以看到:
           
(1)在應用正常情況下,圖中标紅的對象是被回收的,是以不是記憶體洩漏問題
           
(2)在應用頻繁full gc時,标紅的對象即使加live也是未被回收的,因上就是在頻繁full gc時,
        同時又有大量請求進入配置設定記憶體進而處理不過來的問題
           

先從解決問題的角度,看怎樣造成頻繁的full gc?

從分析CMS GC開始
先給個CMS GC的概況:
           
(1)young gc
           
可以看到,當eden滿時,young gc使用的是ParNew收集器
           
)K->K,指回收前後eden+s1(或s2)大小
           
2)2403008K,指可用的young代的大小,即eden+s1(或s2)
           
)K->K,指整個堆大小的變化
(heap=(young+old)+perm;young=eden+s1+s2;s1=s2=young/(survivor ratio+))
           
(2)cms gc
           
當使用CMS收集器時,當開始進行收集時,old代的收集過程如下所示:
           
a)首先jvm根據-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly
     來決定什麼時間開始垃圾收集                
b)如果設定了-XX:+UseCMSInitiatingOccupancyOnly,那麼隻有當old代占用确實達到了
     -XX:CMSInitiatingOccupancyFraction參數所設定的比例時才會觸發cms gc                
c)如果沒有設定-XX:+UseCMSInitiatingOccupancyOnly,那麼系統會根據統計資料自行決定什麼時候
    觸發cms gc;是以有時會遇到設定了80%比例才cms gc,但是50%時就已經觸發了,就是因為這個參數
    沒有設定的原因                
d)當cms gc開始時,首先的階段是CMS-initial-mark,此階段是初始标記階段,是stop the world階段,
     是以此階段标記的對象隻是從root集最直接可達的對象
           
CMS-initial-mark:K(K),名額記時,old代的已用空間和總空間
           
e)下一個階段是CMS-concurrent-mark,此階段是和應用線程并發執行的,所謂并發收集器指的就是這個,
     主要作用是标記可達的對象
           
此階段會列印條日志:CMS-concurrent-mark-start,CMS-concurrent-mark
           
f)下一個階段是CMS-concurrent-preclean,此階段主要是進行一些預清理,因為标記和應用線程是并發執行的,
    是以會有些對象的狀态在标記後會改變,此階段正是解決這個問題
           
因為之後的Rescan階段也會stop the world,為了使暫停的時間盡可能的小,也需要preclean階段先做一部分
    工作以節省時間
           
此階段會列印條日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean
           
g)下一階段是CMS-concurrent-abortable-preclean階段,加入此階段的目的是使cms gc更加可控一些,
     作用也是執行一些預清理,以減少Rescan階段造成應用暫停的時間
           
此階段涉及幾個參數:
           
-XX:CMSScheduleRemarkEdenSizeThreshold(預設m):控制abortable-preclean階段什麼時候開始執行,
即當eden使用達到此值時,才會開始abortable-preclean階段
           
此階段會列印一些日志如下:
           
CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,
CMS:abort preclean due to time XXX                
h)再下一個階段是第二個stop the world階段了,即Rescan階段,此階段暫停應用線程,對對象進行重新掃描并
     标記
           
YG occupancy:(),指執行時young代的情況
           
CMS remark:K(K),指執行時old代的情況
           
此外,還列印出了弱引用處理、類解除安裝等過程的耗時
           
i)再下一個階段是CMS-concurrent-sweep,進行并發的垃圾清理
           
j)最後是CMS-concurrent-reset,為下一次cms gc重置相關資料結構
           
(3)full gc:
           
有2種情況會觸發full gc,在full gc時,整個應用會暫停
           
a)concurrent-mode-failure:當cms gc正進行時,此時有新的對象要進行old代,但是old代空間不足造成的
           
b)promotion-failed:當進行young gc時,有部分young代對象仍然可用,但是S1或S2放不下,
    是以需要放到old代,但此時old代空間無法容納此
           
頻繁full gc的原因
從日志中可以看出有大量的concurrent-mode-failure,是以正是當cms gc進行時,有新的對象要進行old代,
但是old代空間不足造成的full gc
           
程序的jvm參數如下所示:
           
影響cms gc時長及觸發的參數是以下2個:
           
-XX:CMSMaxAbortablePrecleanTime=
           
-XX:CMSInitiatingOccupancyFraction=
           
解決也是針對這兩個參數來的
           
根本的原因是每次請求消耗的記憶體量過大
           

解決

()針對cms gc的觸發階段,調整-XX:CMSInitiatingOccupancyFraction=,提早觸發cms gc,就可以
        緩解當old代達到%,cms gc處理不完,進而造成concurrent mode failure引發full gc
           
()修改-XX:CMSMaxAbortablePrecleanTime=,縮小CMS-concurrent-abortable-preclean階段
        的時間
           
()考慮到cms gc時不會進行compact,是以加入-XX:+UseCMSCompactAtFullCollection
       (cms gc後會進行記憶體的compact)和-XX:CMSFullGCsBeforeCompaction=
       (在full gc4次後會進行compact)參數
           
但是運作了一段時間後,隻不過時間更長了,又會出現頻繁full gc
           
計算了一下heap各個代的大小(可以用jmap -heap檢視):
           
total heap=young+old=
           
young=s1+s2+eden=m
           
young avail=eden+s1=.+.=.
           
s1=/(++)=m
           
s2=s1
           
eden=m
           
old=m
           
可以看到eden大于old,在極端情況下(young區的所有對象全都要進入到old時,就會觸發full gc),
是以在應用頻繁full gc時,很有可能old代是不夠用的,是以想到将old代加大,young代減小
           
改成以下:
           
新的各代大小:
           
total heap=young+old=
           
young=s1+s2+eden=m
           
young avail=eden+s1=.+.=
           
s1=/(++)=m
           
s2=s1
           
eden=m
           
old=m
           
此時的eden小于old,可以緩解一些問題
           
改完之後,運作了2天,問題解決,未頻繁報full gc

https://my.oschina.net/goldwave/blog/168516