問題背景
(下面的所有內容都是根據書上的Serial/Serial Old收集器下的情況)
在《深入理解JVM》一書中的——3.6.3長期存活的對象將進入老年代的介紹中,
一個例子的jvm參數中加了這一行
-XX:+printTenuringDestribution
意思是希望每次新生代gc后,可以跟蹤Survivor區中的對象的年齡分布。
然后還設置了
-XX:MaxTenuringThreshole=1
這是晉升老年代的年齡閾值。
然后在gc日志中,出現了這樣的字眼:
[GC [DefNew Desired Survivor size 524288 bytes, new threshold 1(max 1) - age 1: 414664 bytes, 414664 total
threshold很顯然就是說,設置的晉升老年代的年齡閾值為1,然后下面的age開頭的那行,很明顯就是在描述Survivor中對象的年齡分布。
百度后知道,age 1后面第一個字節是年齡等於這個1的所有對象的內存占用大小;然后后面那個total的bytes值是指年齡<=這個age的對象總共占用的內存大小。
然后就被這個Desired Survivor size給卡住了,這是什么呢?渴望的理想的Survivor大小???
Desired Survivor size和vm參數-XX:TargetSurvivorRatio
要講這個Desired Survivor size就要知道一個參數:-XX:TargetSurvivorRatio
這個參數的含義是:設定survivor區的目標使用率。默認50,即survivor區對象目標使用率為50%
如果只有一個MaxTenuringThreshold,只有大於這個年齡的對象才能晉升老年代的話,肯定不足以應付更加復雜的情況,如果有很多還沒到這個你設置的MaxThreshold的對象呆在Survivor區的話,這樣Survivor區的內存很快就會滿的。所以這個年齡閾值其實是在運行的時候會動態更改的。
首先,我們要明確,我們設置的MaxTenuringThreshold是最大的閾值,然后在運行的過程中,虛擬機會動態計算晉升的閾值。
來看看JVM中的關鍵源碼:
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { //TargetSurvivorRatio默認50,意思是:在回收之后希望survivor區的占用率達到這個比例 size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); size_t total = 0; uint age = 1; assert(sizes[0] == 0, "no objects with age zero should be recorded"); while (age < table_size) {//table_size=16 total += sizes[age]; //如果加上這個年齡的所有對象的大小之后,占用量>期望的大小,就設置age為新的晉升閾值 if (total > desired_survivor_size) break; age++; } uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
這里就可以看到這個Desired survivor size的計算公式了:
desired survivor size = (survivor區容量 * TargetSurvivorRatio)/100(其實就是survivor容量乘以這個targetSurvivorRatio的比值)
這個TargetSurvivorRatio就是上面介紹的那個參數設置的值,默認是50,一般很少會去改。
然后這個sizes數組是個age table,存的是個個年齡的所有對象的總大小。
所以,代碼的意思是,如果<=某個age(設n歲)的對象累加起來的內存大小,大於我們的desiredSurvivorSize的話,就要看這個n值,如果這個n小於我們設的MaxThreshold,那么這次gc的閾值就是這個n,否則就是我們設的maxThreshold。
所以這也是為什么gc日志中會有new threshold 1(max 1)的字眼吧,max是我們設置的,前面那個是動態計算的吧。
晉升老年代的總結
1.擔保機制
新生代中垃圾收集采用的是復制算法,當Survivor區的內存大小不足以裝下一次Minor Gc中所有的存活對象的時候,就啟動擔保機制,將Survivor不夠放的活對象,直接進入到老年代。
2.大對象直接進入老年代
虛擬機提供了個-XX:pretenureSizeThreshold參數,令內存大於這個設置值的對象直接在老年代分配。這個參數只對Serial和ParNew收集器有效,Parallel Scavenge收集器不認識這個參數,一般它也不需要設置,如果遇到必須要設置這個參數的場合,可以考慮ParNew+CMS的收集器組合。
3.長期存活的對象進入老年代
就是上文說的,在Minor gc中,把age大於設置的-XX:MaxTenuringThresholed值的對象晉升到老年代。
這個age是這樣計算的,jvm為每個對象定義了一個對象年齡(Age)計數器,如果對象在Eden出生並經過第一次Minor GC后仍然存活,並能夠被Survivor區容納的話,將被移到Survivor區中,並且對象年齡設為1。
對象在Survivor區中每“熬過”一次Minor GC,年齡就加一歲。
4.動態對象年齡判斷
這里要說明下一個誤區。
書上是這樣講的:如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
然而我們上面分析過了:
1. 不是某個年齡的對象總和,而是<=某個年齡的對象總和。
2.也不一定是大於SurVivor空間的一半,只是默認TargetSurvivorRatio設為50才是一半,應該是根據這個參數才對。
參考文章:
https://blog.csdn.net/foolishandstupid/article/details/77596050——《jvm源碼閱讀筆記[2]:你不知道的晉升閾值TenuringThreshold詳解》
https://blog.csdn.net/zero__007/article/details/52797684——《MaxTenuringThreshold 和 TargetSurvivorRatio參數說明》
https://blog.csdn.net/u014493323/article/details/82921740——《jvm誤區--動態對象年齡判定》
