【JVM】之 动手模拟 老年代GC

文章目录

    • 零、背景简介
    • 一、前半部分代码gc日志分析
      • (1)分析
    • 二、后半部分gc日志分析
      • (2)分析
      • (3)分析 GC日志

回顾下,对象进入老年代的 4 个常见的时机:

  • 躲过15次 GC,年龄达到 15岁时。
  • 动态年龄判定规则,如果Survivor区域内年龄1 + 年龄2 + 年龄n 的对象总和大于 Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁
  • 如果一次 Young GC后存活对象太多无法放入Survivor区,此时直接计入老年代
  • 大对象直接进入老年代

零、背景简介


本主题主要研究 动态年龄判定规则

JVM参数如下:

-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10485760 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
  • -XX:NewSize=10485760: 新生代 10MB
  • -XX:PretenureSizeThreshold=10485760: 老年代 10MB
  • -XX:InitialHeapSize=20971520: 堆总大小 20MB
  • -XX:MaxTenuringThreshold=15: 只要对象年龄达到15岁才会直接进入老年代

如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAIBefSB-1590336245897)(img/2020-05-2119:19.png)]

总代码如下:

public class Demo {

    public static void main(String[] args) {

        byte[] array1 = new byte[2 * 1024 * 1024];
        array1 = new byte[2 * 1024 * 1024];
        array1 = new byte[2 * 1024 * 1024];
        array1 = null;
        
        byte[] array2 = new byte[128 * 1024];
        
        byte[] array3 = new byte[128 * 1024];
        
        
        array3 = new byte[2 * 1024 * 1024];
        array3 = new byte[2 * 1024 * 1024];
        array3 = new byte[128 * 1024];
        array3 = null;
        
        byte[] array4 = new byte[2 * 1024 * 1024];
    }
    
}


一、前半部分代码gc日志分析


public class Demo1 {

    public static void main(String[] args) {

        byte[] array1 = new byte[2 * 1024 * 1024];
        array1 = new byte[2 * 1024 * 1024];
        array1 = new byte[2 * 1024 * 1024];
        array1 = null;

        byte[] array2 = new byte[128 * 1024];

        byte[] array3 = new byte[2 * 1024 * 1024];
    }
}

GC日志:

OpenJDK 64-Bit Server VM (25.162-b12) for linux-amd64 JRE (1.8.0_162-8u162-b12-1-b12), built on Mar 15 2018 17:19:50 by "buildd" with gcc 7.3.0
Memory: 4k page, physical 16306976k(1516380k free), swap 2097148k(2067560k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 
0.075: [GC (Allocation Failure) 0.075: [ParNew: 7821K->538K(9216K), 0.0016980 secs] 7821K->538K(19456K), 0.0017680 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 9216K, used 2752K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  27% used [0x00000000fec00000, 0x00000000fee297d0, 0x00000000ff400000)
  from space 1024K,  52% used [0x00000000ff500000, 0x00000000ff586948, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 concurrent mark-sweep generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3155K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 338K, capacity 388K, committed 512K, reserved 1048576K

(1)分析

  1. 连续创建 3个 2MB的数组,最后置null
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;

【JVM】之 动手模拟 老年代GC_第1张图片

  1. byte[] array2 = new byte[128 * 1024];

会在eden区创建一个 128kb 的数组同时由 array2变量来引用

【JVM】之 动手模拟 老年代GC_第2张图片

  1. byte[] array3 = new byte[2 * 1024 * 1024];

【JVM】之 动手模拟 老年代GC_第3张图片

目前已有 3 × 2MB 和 128KB,这时候再加入 2MB,则会触发 YoungGC

0.075: [GC (Allocation Failure) 0.075: [ParNew: 7821K->538K(9216K), 0.0016980 secs] 7821K->538K(19456K), 0.0017680 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 

gc过后分配在 Eden 区域内的数组,如下:

这时候Survivor From区里的 500KB的对象 满 1岁了。



二、后半部分gc日志分析


用完整代码:

public class Demo {

    public static void main(String[] args) {

        byte[] array1 = new byte[2 * 1024 * 1024];
        array1 = new byte[2 * 1024 * 1024];
        array1 = new byte[2 * 1024 * 1024];
        array1 = null;
        
        byte[] array2 = new byte[128 * 1024];
        
        byte[] array3 = new byte[128 * 1024];
        
        
        array3 = new byte[2 * 1024 * 1024];
        array3 = new byte[2 * 1024 * 1024];
        array3 = new byte[128 * 1024];
        array3 = null;
        
        byte[] array4 = new byte[2 * 1024 * 1024];
    }
    
}

GC日志

OpenJDK 64-Bit Server VM (25.162-b12) for linux-amd64 JRE (1.8.0_162-8u162-b12-1-b12), built on Mar 15 2018 17:19:50 by "buildd" with gcc 7.3.0
Memory: 4k page, physical 16306976k(1914744k free), swap 2097148k(2067560k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 
0.079: [GC (Allocation Failure) 0.079: [ParNew: 7821K->547K(9216K), 0.0010760 secs] 7821K->547K(19456K), 0.0011336 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.080: [GC (Allocation Failure) 0.080: [ParNew: 6829K->0K(9216K), 0.0059605 secs] 6829K->527K(19456K), 0.0060080 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
Heap
 par new generation   total 9216K, used 2212K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  27% used [0x00000000fec00000, 0x00000000fee290e0, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 concurrent mark-sweep generation total 10240K, used 527K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3198K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 343K, capacity 388K, committed 512K, reserved 1048576K


(2)分析

  1. array3
        array3 = new byte[2 * 1024 * 1024];
        array3 = new byte[2 * 1024 * 1024];
        array3 = new byte[128 * 1024];
        array3 = null;
  1. 接着运行byte[] array4 = new byte[2 * 1024 * 1024];

这时候再放 2MB 数组,是放不下的,必然会触发一次 Young GC

【JVM】之 动手模拟 老年代GC_第4张图片


(3)分析 GC日志

  1. 第一次 GC 前面分析过了。
0.079: [GC (Allocation Failure) 0.079: [ParNew: 7821K->547K(9216K), 0.0010760 secs] 7821K->547K(19456K), 0.0011336 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  1. 第二次 GC
0.080: [GC (Allocation Failure) 0.080: [ParNew: 6829K->0K(9216K), 0.0059605 secs] 6829K->527K(19456K), 0.0060080 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 

ParNew: 6829K->0K(9216K)
这行日志表明,这次 GC 之后,新生代直接就没有对象了。

可知 3 × 2MB数组 和 1 × 128KB的数组,被回收掉了。

如图:

【JVM】之 动手模拟 老年代GC_第5张图片

这时候发现 Surivior区域中的对象都是存货的,而且总大小超过 50% ,年龄均为 1岁。

动态年龄判定规则:年龄1 + 年龄2 + 年龄n的对象总大小超过了 Survivor区域的 50%,年龄n以上的对象进入老年代。

然而这里的对象都是 年龄1, 所以直接全部进入老年代。

如图:
【JVM】之 动手模拟 老年代GC_第6张图片

concurrent mark-sweep generation total 10240K, used 527K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
这行日志,表明:
CMS管理的老年代,此时使用空间刚好是 500KB多,证明了Survivor里的对象触发了动态年龄判定规则,虽然没有达到 15岁,但是全部进入老年代了。

最后 array4变量引用的 2MB的数组,此时会被分配到 Eden区域

如图:
【JVM】之 动手模拟 老年代GC_第7张图片

你可能感兴趣的:(【JVM】)