zoukankan      html  css  js  c++  java
  • 关于gc日志中Desired Survivor的疑问和对象晋升老年代的小结

    问题背景

    (下面的所有内容都是根据书上的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误区--动态对象年龄判定》

  • 相关阅读:
    想要学习编程?不如来玩玩这15款游戏!总有一款适合你!
    C++ 高级教程:C++ 文件和流
    4个小众Chrome插件,最后一个互联网人必备!
    程序员必读,熬夜是如何摧残你的身体的!
    教育部将编程教育纳入中小学相关课程,编程正成为全球语言!
    Windows 比 Linux 好?我有 13 个反对理由
    程序员的十八般兵器库,捋一捋这近几年程序员们日常工作中常用的开源工具
    how to train yolov4 on custom dataset
    How to Perform Object Detection With YOLOv3 in Keras
    YOLOv4 / Scaled-YOLOv4 / YOLO
  • 原文地址:https://www.cnblogs.com/wangshen31/p/10427802.html
Copyright © 2011-2022 走看看