-
經過15次GC的對象會進入老年代
-
動態年齡判斷
-
大對象直接進入老年代
-
Minor GC后 的對想太多,無法放入Survivor區怎么辦?
-
老年代空間內存分配擔保
-
老年代垃圾回收算法
首先我們來看下面的圖,我們寫好的代碼在運行時,就會不斷的創建各種各樣的對象,這些對象都會優先放到新生代的Eden區和survivor1區域
接着假如新生代的Eden區和Survivor1區都快滿了,此時就會觸發Minor GC,把存活對象轉移到Survivor2區
如圖:
然后接着就會使用Eden區和Survivor1區繼續為對象分配內存空間
1.躲過15次GC之后進入老年代
默認的設置下,當對象的年齡達到15歲時,也就是躲過15次GC的時候,他就會轉移到老年代去
這個具體是多少歲轉移到老年代,可以通過JVM參數 "-XX:MAX TenuringThreshold" 來設置,默認是15
2.動態年齡判斷
這里有另外一個規則可以讓對象進入老年代,不用等到默認15次GC后才可以。
他們的規則是,假如說當前放對象的Survivor區域里,一批對象的總大小大於zhe塊區域的內存大小的50%,
那么大於等於這批對象年齡的對象,就可以直接進入老年代
如圖:
假如這個圖里Survivor區有兩個對象,這兩個對象的年齡都是一樣的 都是2歲
然后兩對象加起來超過50MB,超過了Survivor2區的內存大小的一半,這個時候,Survivor區里的大於等於2歲的對象,就要全部進入老年代里去了
這就是所謂的動態年齡判斷的規則,這條規則也會讓一些新生代的對象進入老年代
另外一個概念,其實這個規則運行是按照如下邏輯:年齡1+年齡2+年齡的多個對象內存占用總和超過了Survivor區域的50%,此時此時就會把年齡n以上的對象放入老年代
無論是15次GC后進入老年代還是動態對象年齡判斷,都是希望那些可能長期存活的對象,轉移到老年代
3.大對象直接進入老年代
有一個JVM參數,就是"-XX:PretenureSizeThreshold",可以把它設置為字節數,如“1048576”字節,就是1MB
它的意思是,如果要創建一個大於這個內存大小的對象,比如一個超大的數組,或者是別的啥東西,此時就直接把這個對象放入到老年代,就不會進入新生代
之所以這么做,就是要避免新生代里出現那種大對象,然后屢次躲過GC,還得把他在兩個Survivor區域來回復制多次之后才能進入老年代
那么大的對象在內存里 來回復制,是很耗時的過程
所以說這也是一個對象進入老年代的規則
4.Minor GC 后的對象太多無法放入Survivor區怎么辦?
現在有一個比較大的問題,就是如果在Minor GC之后發現剩余的存活對象太多,沒辦法都放入另一塊Survivor區怎么辦?
比如上圖,假如在發生GC的時候,發現Eden區超過150MB的存活對象,此時沒辦法放入Survivor區中,此時該怎么辦呢?
這個時候就必須把這些對象直接轉移到老年代
5.老年代的空間內存分配擔保
首先,在執行任何一次MinorGC 之前,JVM都會檢查老年代可用的可用內存空間,是否大於新生代所有的對象的大小。
因為極端情況下,可能新生代Minor GC過后,所有對象都存活下來了,那豈不是新生代所有對象全部要進入老年代?
如果說發現老年代的內存大小是大於新生代所有對象的內存大小,那么可以放心大膽的進行MinorGC.
但是假如執行了Minor GC之前,發現老年代的可用內存已經小於新生代的全部對象大小
那么這個時候是不是有可能Minor GC 之后 存活下來的對象,全部需要轉移到老年代,而老年代的空間又不夠呢?
理論上,是有可能的
所以假如Minor GC之前,發現老年代的可用內存已經小於了新生代的全部對象大小, 就會看一個"-XX:-HandlePromotionFailure"的參數是否設置了
如果有這個參數,那么就會繼續嘗試進行下一-步判斷。
下一步判斷,就是看看老年代的內存大小,是否大於之前每一-次Minor GC后進入老年代的對象的平均大小。
舉個例子,之前每次Minor GC后,平均都有10MB左右的對象會進入老年代,那么此時老年代可用內存大於10MB。
這就說明,很可能這次Minor GC過后也是差不多10MB左右的對象會進入老年代,此時老年代空間是夠的,看下圖。
如果上面那個步驟判斷失敗了,或者是“ -XX:-HandlePromotionFailure"參數沒設置,此時就會直接觸發-次"FullGC" , 就是對老年代進行垃圾回收,盡量騰出來-些內存空間,然后再執行Minor GC.
如果上面兩個步驟都判斷成功了。那么就是說可以冒點風險嘗試一下Minor GC。此時進行Minor GC有幾種可能。 第一種可能,Minor GC過后。剩余的存活對象的大小。是小於Sunivor區的大小的, 那么此時存活對象進入Survivor區域即可。
第二種可能, Minor GC過后, 剩余的存活對象的大小,是大於Survivor區域的大小,但是是小於老年代可用內存大小的,此時就直接進入老年代即可。
第三種可能,很不幸, Minor GC過后, 剩余的存活對象的大小。大於了Survivor區域的大小,也大於了老年代可用內存的大小。此時老年代都放不下這些存活對象了,就會發生 "Handle Promotion Failure”的情況,這個時候就會觸發一次“FullGC"。
Full GC就是對老年代進行垃圾回收,同時也一般會對新生代進行垃圾回收。
因為這個時候必須得把老年代里的沒人引用的對象給回收掉。然后才可能讓Minor GC過后剩余的存活對象進入老年代里面。
如果要是Full GC過后, 老年代還是沒有足夠的空間存放Minor GC過后的剩余存活對象,那么此時就會導致所謂的"OOM"內存溢出了
因為內存實在是不夠了,你還是要不停的往里面放對象,當然就崩潰了。
這段規則有點燒腦,但是我覺得如果大家仔細對這段文字多看兩遍。然后結合我們的圖,腦子里想一想,基本都能看懂這個規則。
6.老年代垃圾回收算法
其實把上面的內容都看懂之后,大家現在基本就知道了Minor GC的觸發時機,然后就是Minor GC之前要對老年代空間大小做的檢查
包括檢查失敗的時候要提前觸發Full GC給老年代騰一些空間出來,或者是Minor GC過后剩余對象太多放入老年代內存都不夠,也要觸發Full GC。包括這套規則,還有觸發老年代垃圾回收的Full GC時機。都給大家講清楚了。
簡單來說,-句話總結,對老年代觸發垃圾回收的時機,-般就是兩個:
要不然是在Minor GC之前, -通檢查發現很可能Minor GC之后要進入老年代的對象太多了,老年代放不下,此時需要提前觸發Full GC然后再帶着進行Minor GC ;
要不然是在Minor GC之后,發現剩余對象太多放入老年代都放不下了。
那么對老年代進行垃圾回收采用的是什么算法呢?
簡單來說,老年代采取的是標記整理算法,這個過程說起來比較簡單
大家看下圖,首先標記出來老年代當前存活的對象, 這些對象可能是東一個西一 個的。
大家-定要注意一點,這個老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。
如果系統頻繁出現老年代的Full GC垃圾回收,會導致系統性能被嚴重影響,出現頻繁卡頓的情況。