GC -XX:TargetSurvivorRatio 参数实践及gc日志分析

一些和Survivor区域相关的参数含义和解释可以参考:https://blog.csdn.net/z69183787/article/details/104950273,这里着重写一下自己实践后的结论和现象

JVM参数:

-verbose:gc
-Xmx200M
-Xms200M
-Xmn50M
-XX:TargetSurvivorRatio=60
-XX:+PrintTenuringDistribution
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:MaxTenuringThreshold=3

Java代码:因为实际调试后发现gc日志有些奇怪,所以这边分段来进行运行和查看日志

这里先解释一下新生代的大小与参数的含义,JVM参数中 Xmn是50M,按照SurvivorRatio=8的默认配置
eden:survivor:survivor = 40M :5M :5M = 8:1:1
但是实际在使用的时候,由于新生代采取的是复制算法,所以有一个survivor区是不会存储对象的,所以不算在gc日志中的total大小中,所以下图中可以明显看到,par new generation total = 46080K ,是 eden+1个survivor的大小。
其次MaxTenuringThreshold=3,非默认值15,意味着survivor区域的对象可以尽早的进入老年代

1、首先一个空的main函数

可以发现,即使什么对象都没申明,新生代已经占据了差不多5M的空间。(这个5M和实际环境与机器有关,每次可能各不相同)

2、申明3个1M的对象

新生代的使用量变成了8M,多了3M,正是对应的3个byte变量的空间大小

3、接下来的步骤,需要计算一下,因为eden区域有40M,目前used空间有差不多8M,我们需要构造一个大对象,让eden区域变满达到临界状态,这样可以更好的观测每个空间内的数值变化。同时,这边增加了一个构造byte的静态方法,由于是方法内部的局部变量,后期会被gc回收,可以方便的进行后续跟踪。40-8=32M

可以明显看到,现在eden区域已经满了,之后可以尝试minor gc 触发后 新生代的变换情况了。

4、这时我们继续在方法中新增1个10M的byte数组,由于3中的eden已达到100%,所以现在会发生1个minor gc。
这边先描述一下我自己理解的会触发gc的几种情况:https://blog.csdn.net/z69183787/article/details/104938075

根据gc日志可以观察到以下几个数据变化:

  • 由于eden已满,这时我们分配了一个新的对象,导致了Allocation Failure,表明产生了1次Minor gc。
  • ParNew代表了此次使用的是新生代的ParNew收集器,这次gc使得年轻代的大小从40400K减少至3954K,差不多35M左右;
  • 同时由于增加了jvm参数PrintTenuringDistribution,也打印出了desired survivor的取值 = survivor * targetSurvivorRatio = 5M * 60% = 3M,此时survivor区域中有着age=1,并且大小为4M的对象。由于达到了desired survivor 的阈值上限,所以会重新计算对象晋升的age,计算公式为:min(age, MaxTenuringThreshold) = 1,下次年轻代gc时生效;
  • 如果survivor区域中同时有不同age的对象存在,那么从小到大累加,累加过程中达到阈值上限的age将会成为计算公式中的age。言下之意,下一次gc时,大于这个age的对象均会被晋升至老年代
  • 在这次的gc过程中,原eden的40M组成中,3M(byte)+5M(初始)+32M(方法产生),32M由于是方法区内的临时对象直接被gc了,初始的5M数值也被清除了一些,剩余的3M被copy至from survivor区,占比7%。新分配的10M对象在eden区,占比27%;

5、接下来继续加入新对象(32M),用来验证晋升age=1后,是否会在新的一次gc后立刻进入老年代

  • 紧接着上一次的gc,由于eden区已占了10M的空间,所以新增32M的空间后,再次由于eden空间不足,导致了一次新的Minor GC。
  • 将eden区域中原有的10M以及from survivor 中的3M 一起放到to survivor中,由于晋升age已经变成了1,所以13M的对象一同晋升老年代。而新的eden区使用了81%的空间(即新增的32M),两个survivor区清0。同时maxtenuringThreshold也被重置为3。
  • 对比4与5中的from space 与 to space 的地址可以发现,在gc后互换了,满足复制算法的逻辑。

测试代码:

public class GcTest {

    public static void main(String[] args) throws InterruptedException {
        // main方法中分配的对象不会被回收, 不断分配是为了达到TargetSurvivorRatio这个比例指定的值, 即5M*60%=3M(Desired survivor size),说明: 5M为S区的大小,60%为TargetSurvivorRatio参数指定,如下三个对象分配后就能够达到Desired survivor size
        byte[] byte1m_4 = new byte[1 * 1024 * 1024];
        byte[] byte1m_5 = new byte[1 * 1024 * 1024];
        byte[] byte1m_6 = new byte[1 * 1024 * 1024];

        garbage();
        //Thread.sleep(3000);
        System.out.println("------------------------------------------------------");

        // 这次ygc时, 由于s区已经占用达到了60%(-XX:TargetSurvivorRatio=60), 所以会重新计算对象晋升的age,计算公式为:min(age, MaxTenuringThreshold) = 1
        byte[] a = new byte[10*1024*1024];

        // 由于前一次ygc时算出age=1, 所以这一次再ygc时, byte1m_4, byte1m_5, byte1m_6就会晋升到Old区, 而不需要等MaxTenuringThreshold这么多次, 此次ygc后, s0(from)和s1(to)空间中对象再次被清空, 对象全部晋升到old
        garbage();
        //Thread.sleep(3000);

        //System.out.println("------------------------------------------------------");
        //byte[] bbb = new byte[2*1024*1024];

        //System.out.println("hello world");
    }
    private static void garbage(){
        byte[] byte1m = new byte[32 * 1024 * 1024];
    }
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值