JVM年轻代和老年代垃圾回收
復制算法
- 概述
復制算法將內存劃分為兩個區間,在任意時間點,所有動態分配的對象都只能分配在其中一個區間(稱為活動區間),而另外一個區間(稱為空閑區間)則是空閑的。
? 當有效內存空間耗盡時,JVM將暫停程序運行,開啟復制算法GC線程。接下來GC線程會將活動區間內的存活對象,全部復制到空閑區間,且嚴格按照內存地址依次排列,與此同時,GC線程將更新存活對象的內存引用地址指向新的內存地址。
- 復制算法優點
- 復制算法的優化
內存劃為1個Eden區,2個Survivor區,其中Eden區占80%內存空間,每一塊Survivor區各占10%內存空間,比如說Eden區有800MB內存,每一塊Survivor區就100MB內存。
Survivor區,一塊叫From,一塊叫To,對象存在Eden和From塊。當進行GC時,Eden存活的對象全移到To塊,而From中,存活的對象按年齡值確定去向,當達到一定值的對象會移到年老代中,沒有達到值的復制到To區,經過GC后,Eden和From被清空。之后,From和To交換角色,新的From即為原來的To塊,新的To塊即為原來的From塊,且新的To塊中對象年齡加1。
注:年齡閾值可通過-XX:MaxTenuringThreshold可設置
MinorGC過程
- 觸發時機:
Eden區滿時會觸發,但是Survivor區域滿了不會引發GC
- 解釋
代碼在運行的過程中,就會不斷的創建各種各樣的對象,這些對象都會優先放到新生代的Eden區和Survivor1區。
假如新生代的Eden區和Survivor1區都快滿了,此時就會觸發Minor GC,把存活對象轉移到Survivor2區去。此時就會把Eden區中的存活對象都一次性轉移到一塊空著的Survivor區。接著Eden區就會被清空。
然后再次分配新對象到Eden區里,Eden區和一塊Survivor區里是有對象的,其中Survivor區里放的是上一次Minor GC后存活的對象。如果下次再次Eden區滿,那么再次觸發Minor GC,就會把Eden區和放著上一次Minor GC后存活對象的Survivor區內的存活對象,轉移到另外一塊Survivor區去。
對象進入老年代的情況
根據對象年齡
對象每次在新生代里躲過一次MinorGC然后被轉移到一塊Survivor區域中,那么它的年齡就會增長一歲,默認的設置下,當對象的年齡達到15歲的時候,也就是躲過15次GC的時候,他就會轉移到老年代里去。這個具體是多少歲進入老年代,可以通過JVM參數“-XX:MaxTenuringThreshold”來設置,默認是15歲。
動態對象年齡判斷
還有另外一個規則可以讓對象進入老年代,不用等待15次GC過后才可以。
當前Survivor區域里,一批對象的總大小超過了這塊Survivor區域的內存大小的50%,那么此時大于等于這批對象年齡的對象,就可以直接進入老年代了。
假設這個圖里的Survivor2區有三個對象,這些對象的年齡一樣,都是3歲然后倆對象加起來對象超過了50MB,超過了Survivor2區的100MB內存大小的一半了,這個時候,Survivor2區里的大于等于3歲的對象,就要全部進入老年代里去。這就是所謂的動態年齡判斷的規則,這條規則也會讓一些新生代的對象進入老年代。
另外這里要理清楚一個概念,就是實際這個規則運行的時候是如下的邏輯:年齡1+年齡2+年齡n的多個年齡對象總和超過了Survivor區域的50%,此時就會把年齡n以上的對象都放入老年代。
大對象直接進入老年代
有一個JVM參數,就是“-XX:PretenureSizeThreshold”,可以把他的值設置為字節數,比如“1048576”字節,就是1MB。意思就是,如果你要創建一個大于這個大小的對象,比如一個超大的數組,或者是別的啥東西,此時就直接把這個大對象放到老年代里去。不會經過新生代。之所以這么做,就是要避免新生代里出現那種大對象,然后屢次躲過GC,然后在兩個Survivor區域里來回復制多次之后才能進入老年代,
Minor GC后的對象太多
在Minor GC之后發現剩余的存活對象太多了,沒辦法放入另外一塊Survivor區,此時就必須得把這些對象直接轉移到老年代去
老年代空間分配擔保規則
- 問題引出
如果新生代里有大量對象存活下來,確實是自己的Survivor區放不下了,必須轉移到老年代去,那么如果老年代里空間也不夠放這些對象怎么處理?
- 解決
首先,在執行任何一次Minor GC之前,JVM會先檢查一下老年代可用的可用內存空間,是否大于新生代所有對象的總大小。防止最極端的情況下,可能新生代Minor GC過后,所有對象都存活下來了新生代所有對象全部要進入老年代。
如果說發現老年代的內存大小是大于新生代所有對象的,此時就可以對新生代發起一次Minor GC了,因為即使Minor GC之后所有對象都存活,Survivor區放不下了,也可以轉移到老年代去。
假如執行Minor GC之前,發現老年代的可用內存已經小于了新生代的全部對象大小了那么這個時候有可能在Minor GC之后新生代的對象全部存活下來,然后全部需要轉移到老年代去,但是老年代空間又不夠。所以假如Minor GC之前,就會看一個“-XX:-HandlePromotionFailure”的參數是否設置了 ,如果有這個參數,那么就會繼續嘗試進行下一步判斷。
-
- 查看老年代的內存大小,是否大于之前每一次Minor GC后進入老年代的對象的平均大小。
例如:之前每次Minor GC后,平均都有10MB左右的對象會進入老年代,那么此時老年代可用內存大于10MB。這就說明,很可能這次Minor GC過后也是差不多10MB左右的對象會進入老年代,此時老年代空間是夠的,就可以進行回收。
-
- 如果上面那個步驟判斷失敗了,或者是“-XX:-HandlePromotionFailure”參數沒設置,此時就會直接觸發一次“Full GC”,就是對老年代進行垃圾回收,盡量騰出來一些內存空間,然后再執行Minor GC。
-
總結
在發生Minor GC之前,虛擬機會檢查老年代最大可用的連續空間是否大于新生代所有對象的總空間。
老年代垃圾回收算法
標記 - 整理算法
復制收集算法在對象存活率較高時需要進行較多的復制操作,效率將會降低。更關鍵的是,如果不想浪費50%的內存空間,就需要提供額外的空間進行分配擔保。由于老年代中對象存活率較高,而且找不到其他內存進行分配擔保,所以老年代一般不能直接選用這種收集算法。
根據老年代的特點,有人對“標記 - 清除”進行改進,提出了“標記 - 整理”算法。“標記 - 整理”算法的標記過程與“標記 - 清除”算法相同,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存。
觸發時機
調用System.gc時,系統建議執行Full GC,但是不必然執行
老年代空間不足
空間分配擔保失敗
JDK 1.7 及以前的永久代(方法區)空間不足
通過Minor GC后進入老年代的平均大小大于老年代的可用內存
CMS GC處理浮動垃圾時,如果新生代空間不足,則采用空間分配擔保機制,如果老年代空間不足,則觸發Full GC
Old GC執行的時帶上一次Young GC
- OldGC觸發條件
發生Young GC之前進行檢查,如果“老年代可用的連續內存空間” < “新生代歷次Young GC后升入老年代的對象總和的平均大小”,說明本次Young GC后可能升入老年代的對象大小,可能超過了老年代當前可用內存空間此時必須先觸發一次Old GC給老年代騰出更多的空間,然后再執行Young GC
執行Young GC之后有一批對象需要放入老年代,此時老年代就是沒有足夠的內存空間存放這些對象了,此時必須立即觸發一次Old GC(老年代剩余空間大于歷次年輕代進入老年代的平均大小,但是本次回收后進入老年代的對象遠大于歷次的平均大小)
老年代內存使用率超過了92%,也要直接觸發Old GC,當然這個比例是可以通過參數調整的
簡單點說就是,就是老年代空間也不夠了,沒法放入更多對象了,這個時候務必執行Old GC對老年代進行垃圾回收。
- 解釋
如果是條件1引起的Old GC 那么說明老年代空間不足,無法進行Young GC,需要先進行一次Old GC然后再進行Young GC,這樣Old GC就發生在 Yonug GC之前
如果是條件2引起的,那么就是Young GC后空間不足,進而引發Old GC
在很多JVM的實現機制里,其實在上述幾種條件達到的時候他觸發的實際上就是Full GC,這個Full GC會包含Young GC、Old GC和永久代的GC
總結
以上是生活随笔為你收集整理的JVM年轻代和老年代垃圾回收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中小卖家电商节恐惧症:你们剁手,我们割肉
- 下一篇: opencv保存视频文件很大