一、计算规则

有一个关键参数TargetSurvivorRatio。这个参数是年轻代对象动态晋升老年代的关键参数。下面是虚拟机中晋升老年代计算的代码。

  1. uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
  2. //survivor_capacity是survivor空间的大小
  3. size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
  4. size_t total = 0;
  5. uint age = 1;
  6. while (age < table_size) {
  7. total += sizes[age];//sizes数组是每个年龄段对象大小
  8. if (total > desired_survivor_size) break;
  9. age++;
  10. }
  11. uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
  12. ...
  13. }

翻译上面的代码就是:

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再晋级老年代。而是通过这种方式来实现老年代的晋升。