一、计算规则
有一个关键参数TargetSurvivorRatio。这个参数是年轻代对象动态晋升老年代的关键参数。下面是虚拟机中晋升老年代计算的代码。
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//survivor_capacity是survivor空间的大小
size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
total += sizes[age];//sizes数组是每个年龄段对象大小
if (total > desired_survivor_size) break;
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}
翻译上面的代码就是:
1、通过survivor区内存值的大小和这个关键比率来计算一个期望值,desired_survivor_size 。
2、定义一个total计数器,累加每个年龄段对象大小的总和。
3、当total大于期望值desired_survivor_size 时停止累加。
4、比较当前age和MaxTenuringThreshold 哪个更小,将其作为下一次晋升老年代的基准值
我们可以通过-XX:TargetSurvivorRatio这个参数来制定这个比率,但是议案不会调整他。
大致逻辑就是,遍历对象的时候,从年龄由小到大进行累加,当加到某个年龄之后,发现内存总和大于了那个上面计算的那个期望值,就从这个年龄段开始把以上的对象都放在老年代中
二、为什么这样做?
但是我们应该能想到,不是有个-XX:MaxTenuringThreshold这个参数来指定最大对象吗,这里为什么还要做一个动态的判断呢?
JVM引入动态年龄计算,主要基于如下两点考虑:
1、如果没有动态计算,固定按照MaxTenuringThreshold设定的阈值作为晋升条件:
a)MaxTenuringThreshold设置的过大,原本应该晋升的对象一直停留在Survivor区,直到Survivor区大小不足以满足完成一次minorGC时,jvm会将Eden+Svuvivor中存活的一股脑全部移动到老年代,不管对象的年龄是多少。这样对象老化的机制就失效了。
b)MaxTenuringThreshold设置的过小,那么对象就会更早的被移动到老年代,对象不能在新生代充分被回收,大量短期对象被晋升到老年代,老年代空间迅速增长,引起频繁的Major GC。分代回收失去了意义,严重影响GC性能。
2、应用程序在不同时间节点上的请求量与并发量都不同:特殊任务的执行或者流量成分的变化,都会导致对象的生命周期分布发生波动,那么固定的阈值设定,因为无法动态适应变化,会造成和上面相同的问题。
总结来说,为了更好的适应不同程序的内存情况,虚拟机并不总是要求对象年龄必须达到Maxtenuringthreshhold再晋级老年代。而是通过这种方式来实现老年代的晋升。