什么情況下會發生full Gc?如何排查頻繁發生full Gc的原因?


GC就是Java的垃圾回收機制,要了解什么情況下會發生GC(即GC得觸發條件),我們需要先了解JVM的內存模型結構,之前一篇文章已經詳細講解了Jvm的內存模型結構,而通常來說,GC主要針對的是堆(java heap)區。

而java heap是分代的(年輕代和老年代),為什么要分代?其實也不難理解,分代就是為了優化性能,如果不分代,那就會導致所有對象揉在一塊,那樣GC就會對堆區域進行全掃描。所以,分代可以大大提升GC性能,那么,分代的原理是什么?

JVM對於堆的垃圾回收,采用分代收集的策略,所以分代的原理就是根據堆中對象的存活周期進行分代,年輕代中,每次垃圾回收都有大批對象死去,只有少量存活,而老年代中存放的對象存活率高。

>>>>>必須知道的知識點<<<<<
Young space:年輕代(新生代),保存生命周期較短的對象

Tenured space:老年代(年老代),保存生命周期較長的對象

Minor GC:發生在Young space中的gc

Major GC:發生在老年代Tenured space中的gc

STW(stop the world):指的是用戶線程在運行至安全點(safe point)或安全區域(safe region)之后,就自行掛起,進入暫停狀態,對外的表現就是卡頓,而不論何種gc算法,不論是minor gc還是major gc都會STW,區別只在於STW的時間長短。

Full GC:無官方定義,通常意義上而言指的是一次特殊GC的行為描述,這次GC會回收整個堆的內存,包含老年代,新生代,metaspace等。
但是實際情況中,我們主要看的是gc.log日志,其中也會發現在部分gc日志頭中也有Full GC字眼,此處表示含義是在這次GC的全過程中,都是STW的狀態,也就是說在這次GC的全過程中所有用戶線程都是處於暫停的狀態。

>>>>>年輕代<<<<<
Jvm把年輕代分三部分:1個Eden(伊甸園)區和2個Survivor(幸存者)區(分別叫from和to),默認比例為8:1。

為啥默認這個比例?
一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理),這些對象經過第一次Minor GC后,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度(默認15歲)時,就會被移動到年老代中。

因為年輕代中的對象基本都是朝生夕死的(80%以上),所以年輕代的垃圾回收算法采用復制算法(內存分為兩塊,每次只用其中一塊,當一塊內存用完,就將還活着的對象復制到另外一塊內存上,復制算法不產生內存碎片)。在GC開始的時候,對象只會存在於Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接着進行GC,Eden區中所有存活的對象都會被復制到“To”,而在“From”區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被復制到“To”區域。經過這次GC后,Eden區和From區已經被清空。

這個時候,“From”和“To”會交換他們的角色,就是新的“To”是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重復這樣的過程,直到“To”區被填滿,“To”區被填滿之后,會將所有對象移動到年老代中。

總結下來,JVM的堆區對象分配的規則一般如下:
1)對象優先在Eden區分配

2)大對象直接進入老年代(-XX:PretenureSizeThreshold=3145728 該參數來定義進入老年代對象大小)

3)長期存活的對象將進入老年代(在JDK8中-XX:MaxTenuringThreshold=1的閥值設定根本沒用)

4)動態對象年齡判定(虛擬機並不會永遠地要求對象的年齡都必須達到MaxTenuringThreshold才能晉升老年代,如果Survivor空間中相同年齡的所有對象的大小總和大於Survivor的一半,年齡大於或等於該年齡的對象就可以直接進入老年代)

5)空間分配擔保

6)只要老年代的連續空間大於(新生代所有對象的總大小或者歷次晉升的平均大小)就會進行minor GC,否則會進行full GC

GC的觸發條件
PS:JVM優化的目的就是減少SWT執行的時間(避免卡頓),避免頻繁full gc
1)System.gc()方法的調用。
此方法的調用是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發 Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數。強烈影響系建議能不使用此方法就別使用,讓虛擬機自己去管理它的內存,可通過通過-XX:+ DisableExplicitGC來禁止RMI(Java遠程方法調用)調用System.gc。

2)舊生代空間不足。舊生代空間只有在新生代對象轉入及創建為大對象、大數組時才會出現不足的現象,當執行Full GC后空間仍然不足,則拋出錯誤:java.lang.OutOfMemoryError: Java heap space 。為避免以上兩種狀況引起的FullGC,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創建過大的對象及數組。

3)Permanet Generation空間滿了。Permanet Generation中存放的為一些class的信息等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下會執行Full GC。如果經過Full GC仍然回收不了,那么JVM會拋出錯誤信息:java.lang.OutOfMemoryError: PermGen space 。為避免Perm Gen占滿造成Full GC現象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。

4)通過Minor GC后進入老年代的平均大小大於老年代的可用內存

5)由Eden區、From Space區向To Space區復制時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代可用內存不足(老年代可用內存小於該對象)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM