1. 进入老年代的四种常见时机:

  • 躲过15次 gc;
  • 动态对象年龄判断规则,如果 Survivor 区内年龄1…年龄 n 的对象总和超过 Survivor 区的50%,此时年龄 n 以上的对象进入老年代;
  • Young GC 后存活对象太多,Survivor 放不下,放入老年代;
  • 大对象直接放入老年代;

这里通过代码演示动态对象年龄判断规则的时机,也是最常见的对象进入老年代的情况:

2. JVM 参数

  1. -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

image.png

3. 动态年龄判定规则的部分示例代码

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];
    }
}

4. 触发一次 Young GC 日志分析

:::warning 代码在 IDEA 中运行;
可能是运行环境的影响,每次实验时都会有额外的 2M 左右未知对象被创建,且GC后会有 2M 的未知对象进入老年代,对实验的影响很大….
做实验时需要将这额外的 2M 未知对象考虑进去!
第一段代码运行结束后:

  • 老年代有 2M 未知的对象;(2M)
  • Eden 有 2M 的数组对象,2.4M的未知对象;(4.4M)
  • From Survivor 有 128K 数组,几百K的数组未知对象 ;(675K)

第二段代码运行结束后:

  • 老年代有 2M 未知对象,2M 未知对象,几百K的数组未知对象,128K的数组;(4.7M)
  • Eden 有 2M 的数组对象,2.4M的未知对象;(4.4M)
  • From Survivor 有未知对象(326K) ::: ```verilog Java HotSpot(TM) 64-Bit Server VM (25.121-b13) for windows-amd64 JRE (1.8.0_121-b13), built on Dec 12 2016 18:21:36 by “java_re” with MS VC++ 10.0 (VS2010) Memory: 4k page, physical 20883216k(8767920k free), swap 30795604k(4714516k 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:-UseLargePagesIndividualAllocation -XX:+UseParNewGC

0.183: [GC (Allocation Failure) 0.183: [ParNew: 6313K->675K(9216K), 0.0017704 secs] 6313K->2725K(19456K), 0.0032088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap par new generation total 9216K, used 5220K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 55% used [0x00000000fec00000, 0x00000000ff0706c0, 0x00000000ff400000) from space 1024K, 65% used [0x00000000ff500000, 0x00000000ff5a8cf0, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) concurrent mark-sweep generation total 10240K, used 2050K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) Metaspace used 3409K, capacity 4496K, committed 4864K, reserved 1056768K class space used 380K, capacity 388K, committed 512K, reserved 1048576K

<a name="li5pw"></a>
#### (1) 代码在内存中的运行:
```java
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];

image.png

(2) 触发一次 Young GC 之后

  • “ParNew: 6313K->675K(9216K), 0.0017704 secs”
    • GC 之前年轻代的内存:6MB的3个数组 + 128KB的1个数组 + 几百KB的额外数组信息的未知对象;
    • GC 之后年轻代的内存:存活对象大概 675KB = 128KB的数组 + 几百KB的未知对象;
  • “from space 1024K, 65% used”
    • From Survivor 占用了大概 650KB,即存活对象转入了 From Survivor;
  • “eden space 8192K, 55% used”
    • Eden 区占用了 4.4M,除了存放 array3 的2MB,可能还有其他的一些未知对象(IDEA相关的??)

image.png

5. 修改代码,触发二次 Young 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];

        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];
    }
}

(1) 继续往下运行代码,分析内存运行

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];

image.png
image.png

(2) 最终版的 GC 日志分析

Java HotSpot(TM) 64-Bit Server VM (25.121-b13) for windows-amd64 JRE (1.8.0_121-b13), built on Dec 12 2016 18:21:36 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 20883216k(9322716k free), swap 30795604k(5057364k 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:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 

0.200: [GC (Allocation Failure) 0.200: [ParNew: 6313K->697K(9216K), 0.0021057 secs] 6313K->2748K(19456K), 0.0023347 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.203: [GC (Allocation Failure) 0.203: [ParNew: 7209K->326K(9216K), 0.0035184 secs] 9259K->5088K(19456K), 0.0035857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

Heap
 par new generation   total 9216K, used 4843K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  55% used [0x00000000fec00000, 0x00000000ff069698, 0x00000000ff400000)
  from space 1024K,  31% used [0x00000000ff400000, 0x00000000ff4518d0, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 concurrent mark-sweep generation total 10240K, used 4762K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3496K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K
  • 第一次 Young GC 日志已经分析过了:”ParNew: 6313K->697K(9216K), 0.0021057 secs”;
  • 第二次 Young GC 日志分析:
    • “ParNew: 7209K->326K(9216K), 0.0035184 secs”,”from space 1024K, 31% used”
      • GC 之前的新生代的内存,6M的3个数组 + 128KB的2个数组+1M的两组未知对象 ≈ 7.2M;
      • GC 之后的新生代的内存,From Survivor 区中的存活对象 326K(未知对象);
  • 运行环境的影响太大了,不好分析…………….

6. 存活对象放不下 Survivor 而进入老年代的示例代码

public class Demo2 {
    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];

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

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

(1) GC 日志(环境影响不好分析)

Java HotSpot(TM) 64-Bit Server VM (25.121-b13) for windows-amd64 JRE (1.8.0_121-b13), built on Dec 12 2016 18:21:36 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 20883216k(9927868k free), swap 30795604k(4603600k 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:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 

0.154: [GC (Allocation Failure) 0.154: [ParNew: 6313K->682K(9216K), 0.0015879 secs] 6313K->2732K(19456K), 0.0017931 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

Heap
 par new generation   total 9216K, used 5228K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  55% used [0x00000000fec00000, 0x00000000ff0706b0, 0x00000000ff400000)
  from space 1024K,  66% used [0x00000000ff500000, 0x00000000ff5aa9f0, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 concurrent mark-sweep generation total 10240K, used 2050K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3496K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

:::info

  • Young GC 之后,发现存活下来的对象有 2M 的数组(array1),几百KB的未知对象;
  • 此时,并不是全部的存活对象都进入老年代,而是部分对象进入老年代(array1 2M的数组),部分的对象(几百K的未知对象)进入 Survivor; ::: image.png