天天看點

jvm記憶體配置設定與回收政策幹貨

概述

java技術體系中所提倡的自動記憶體管理最終可以歸結為自動化地解決兩個問題:給對象配置設定記憶體以及回收配置設定給對象的記憶體。

對象的記憶體配置設定,往大方向将,就是堆上配置設定(但也可能經過JIT編譯後被拆散為标量類型并間接地棧上配置設定),對象主要配置設定在新生代的Eden區上,如果啟動了本地線程配置設定緩沖,将按線程優先在TLAB上配置設定。少數情況也可能會直接配置設定在老年代中,配置設定的規則并不是百分之百固定,其細節取決于目前使用的是哪一種垃圾收集器組合,還有虛拟機中記憶體相關的參數的設定。

Minor GC和Full GC

  • 新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為java對象大多數都具備朝生夕滅的特性,是以Minor GC非常頻繁,一般回收速度也比較快。
  • 老年代GC(Major GC / Full GC):指發生在老年代的GC,出現Major GC,經常會伴随至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集政策裡就有直接進行Major GC的政策選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

對象優先在Eden配置設定

大多數情況下,對象在新生代Eden區中配置設定。當Eden區沒有足夠空間進行配置設定時,虛拟機将發起一次Minor GC。

虛拟機提供了 -XX:+PrintGCDetails這個收集器日志參數,高數虛拟機在發生垃圾收集行為時列印記憶體回收日志,并且在程序退出的時候輸出目前的記憶體各區域配置設定情況。在實際應用中,記憶體回收日志一般是列印到檔案後通過日志工具進行分析。

大對象直接進入老年代

大對象

指需要大量連續記憶體空間的java對象,最典型的大對象就是那種很長的字元串以及數組(例如byte[ ]數組就是典型的大對象),大對象對虛拟機的記憶體配置設定來說就是一個壞消息(比遇到一個大對象更加壞的消息就是遇到一群“朝生夕滅”的大對象),經常出現大對象容易導緻記憶體還有不少空間時就提前觸發垃圾收集以擷取足夠的連續空間來“安置”它們。

虛拟機提供了一個-XX:PretenureSizeThreshold參數,令大于這個設定值的對象直接在老年代配置設定。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的記憶體複制(新生代采用複制算法收集記憶體)。

注意:PretenureSizeThreshold參數隻對Serial和ParNew兩款收集器有效,Parallel Scavenge收集器不認識這個參數,Parallel Scavenge收集器一般并不需要設定。如果遇到必須使用此參數的場合,可以考慮ParNew加CMS的收集器組合。

長期存活的對象将進入老年代

虛拟機給每個對象定義了一個對象年齡(Age)計數器。如果對象在Eden出生并經曆過第一次Minor GC後仍然存活,并且能被Survivor容納的話,将被移動到Survivor空間中,并且對象年齡設為1.對象在Survivor區中每“熬過”一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(預設15歲),就會被晉升到老年代中。對象晉升老年代的年齡門檻值,可以通過參數-XX:MaxTenuringThreshold設定。

動态對象年齡判定

為了更好地适應不同程式的記憶體狀況,虛拟機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

空間配置設定擔保

在發生Minor GC之前,虛拟機會先檢查老年代最大可用的連續空間是否大于新生代所有對象總空間,如果這個條件成立,那麼Minor GC可以確定是安全的。如果不成立,則虛拟機會檢視HandlePromotionFailure設定值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大于曆次晉升到老年代對象的平均大小,如果大于,将嘗試一次Minor GC,盡管這次Minor GC是有風險的;如果小于,或者HandlePromotionFailure設定不允許冒險,那這時也要改為進行一次Full GC。

下面解釋一下“冒險”是冒了什麼風險,前面提到過,新生代使用複制收集算法,但是為了記憶體使用率,隻使用其中一個Survivor空間來作為輪換備份,是以當出現大量對象在Minor GC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有對象都存活),就需要老年代進行配置設定擔保,把Survivor無法容納的對象直接進入老年代。但前提是老年代本身還有容納這些對象的剩餘空間,一共有多少對象會活下來在實際完成記憶體回收之前是無法明确知道的,是以隻好取之前每一次回收晉升到老年代對象容量的平均值作為經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。

取平均值進行比較其實仍然是一種動态機率的手段,也就是說,如果某次Minor GC存活後的對象突增,遠遠高于平均值的話,依然會導緻擔保失敗。如果出現擔保失敗,那就隻好在失敗後重新發起一次Full GC。雖然擔保失敗時繞的圈子是最大的,但大部分情況下都還是會将HandlePromotionFailure開關打開,避免Full GC過于頻繁。

注意:在JDK 6 Update 24之後,這個測試結果會有差異,HandlePromotionFailure參數不會再影響到虛拟機的空間配置設定擔保政策,觀察OpenJDK中的源碼變化,雖然源碼中還定義了HandlePromotionFailure參數,但是在代碼中已經不會再使用了。JDK 6 Update 24之後的規則變為隻要老年代的連續空間大于新生代對象總大小或者曆次晉升的平均大小就會進行Minor GC,否則将進行Full GC。

–摘自《深入了解java虛拟機》