MaxTenuringThreshold该参数用于控制对象经过GC多少次仍然存活后晋升到老年代的最大阈值,参数最大可配置为15,即对象最多经过15次GC后仍然存活就会晋升到老年代
记住!是最大!也就是说实际上不一定会经过15次才能晋升!这个值的JVM内部会进行动态计算然后动态改变的。
最近在线上有个应用GC非常频繁,MaxTenuringThreshold参数配置的是15,而以前对这个参数的理解是一定要经过15次后才会晋升,所以导致整个排查过程根本就不对,线上日志如下:
Desired survivor size 184549376 bytes, new threshold 1 (max 15)
- age 1: 369096960 bytes, 369096960 total
....下面进行GC,年轻代有很多对象全跑到了老年代
当时思考方向是:age为1,离15有很大的距离,不应该会晋升,老年代空间增加是因为大对象
这个思考方向真是大错特错了,因为对MaxTenuringThreshold理解错了。注意日志中有几个关键字
new threshold 1 (max 15)
意思是说阈值现在变成了1,而不是15!所以直接就晋升了!那么为什么会变成1呢,这个动态计算的算法是怎么样的,参考了一下一些大神的文章再看了下源码,终于发现原因所在,直接看源码会比较清晰,源码如下:
int ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//TargetSurvivorRatio默认为50
//desired_survivor_size = survivor的空间*50%
size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
int age = 1;
assert(sizes[0] == 0, "no objects with age zero should be recorded");
while (age < table_size) {
// 循环遍历所有年龄代的对象累加得到一个大小
total += sizes[age];
// 如果该大小大于desired_survivor_size,即survivor的空间*50%,那么退出循环
if (total > desired_survivor_size) break;
age++;
}
// 如果算出来的age大于MaxTenuringThreshold则使用MaxTenuringThreshold,否则使用计算出来的age
int result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
if (PrintTenuringDistribution || UsePerfData) {
if (PrintTenuringDistribution) {
gclog_or_tty->cr();
// 这里就是线上出现的那个日志所在的地方
gclog_or_tty->print_cr("Desired survivor size %ld bytes, new threshold %d (max %d)",
desired_survivor_size*oopSize, result, MaxTenuringThreshold);
}
//....
}
// 返回计算的年龄
return result;
}
经过多次GC,存活的对象会在From和To两个地方来回的存放,而前提是两个空间能有足够的大小去存放这些数据,上述算法中,会计算每个年龄的大小,如果达到某个年龄后发现总大小以及大于Survivor的大小的50%,那么这时候就需要调整阈值,不能继续等到15次GC后才晋升,这样会导致Survivor的空间不足,所以调整阈值,让其尽快晋升
回到线上的那个例子,线上我记得是没配置TargetSurvivorRatio属性,那么按照50%算,survivor * 50%=184549376,在while循环中计算第一个年龄代=369096960>184549376,那么退出循环,此时计算得到的年龄为1,所以这个年龄的对象会提前晋升
参考文章:
https://www.jianshu.com/p/f91fde4628a5
https://blog.csdn.net/FoolishAndStupid/article/details/77596050?fps=1&locationNum=1
网友评论