Hotspot动态年龄计算

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java Hotspot虚拟机在垃圾回收中如何甄别所分代年龄

title: Hotspot动态年龄计算

date: 2022-01-29 15:00:00

categories: Java

description: JVM 调优


1. 目录

Java-big

2. 题外话

年底事情不是特别多,就和以前论坛好友聊了聊,又说到了 JVM 的垃圾回收机制,其中 Hotspot 对对象年龄的判断以前我没怎么太深入去挖掘,正好趁着这几天得个空,弥补下。

3. 背景

Hotspot 的垃圾回收机制主要针对堆和方法区中,由于 中的非常频繁创建对象,所以这是 GC 工作频繁的区域,而 方法区 相对来比较稳定,不需要分代机制,所以直接 GC 。在 中的频繁创建对象和回收对象占用空间,就会根据对象的年龄进行判断, Hotspot 除了根据配置的年龄进行垃圾回收之外,还有一套动态年龄计算的机制,保证 Hotspot 高效稳定的运作。 说到动态年龄计算规则,那先看下对象默认年龄的计算规则 假设对象首先在 Eden 区域分配,在一次新生代垃圾回收后,如果该对象还存活,则会进入 Survivor ,并且对象的年龄会在 0 基础上加 1 ,该对象在 Survivor 每经历一次垃圾回收,那么它的年龄都会叠加,当年龄叠加到一定阈值(默认 15 ),该对象就被从 Young Generation 晋升到 Tenured Generation 中。

年龄的阈值可被修改,参数为 -XX:MaxTenuringThreshold (可以通过 -XX:+PrintTenuringDistribution 来打印出当次 GC 后的 Threshold)。

4. 概述

说到 Hotspot 动态年龄计算,先了解 内存申请流程 以及 动态年龄的计算规则,方便以下展开描述过程中有更好的理解。

5. 基本概念

5.1. 动态规则

Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 Survivor 区的 50% 时,当然这个 50% 是可配置的,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值。

5.1.1. 内存申请流程

内存申请流程

上图为 Hotspot 的内存申请流程,包括 存栈判断、对象大小判断、年龄判断等。我们可以看出内存分配过程中先判断是否 存栈空间:

  • 在 栈 中垃圾回收更方便,栈帧结束,栈中的对象也随之消亡,所以这里不做过多描述。
  • 如果不在 栈 中分配,会首先判断该对象的大小,超过 Survivor 大小的对象也直接会被放入 tenured generation ;否则会对象会被分配进入 Eden。 本章节也着重讨论这一种场景。

5.1.2. 小结

下面针对对象在堆中的分配的两种情况,即 大对象 分配以及 普通对象 分别做阐述。

5.2. 大对象定义

这里说的大对象主要指的是 在经历过 Young GC 后,因为 Survivor 并不能放下该对象,从而直接使该对象的内存分配在 tenured generation

有两种情况,对象会直接分配到老年代

  • 如果在新生代分配失败且对象是一个不含任何对象引用的大数组,可被直接分配到老年代。通过在老年代的分配避免新生代的一次垃圾回收。
  • PretenureSizeThreshold 可分配到新生代对象的大小限制,任何比这个大的对象都不会尝试在新生代分配,将在老年代分配内存。 PretenureSizeThreshold 默认值是 0,意味着任何对象都会现在新生代分配内存。

6. 基本概念

6.1. 手工设置大对象

6.1.1. 场景假定

  • MaxTenuringThreshold 假定为 10
  • Xmn= 20M
  • XX:SurvivorRatio = 8, EdenSurvivor 比例 为 8

    • Eden = 16M
    • s0、s1 = 2M
  • PretenureSizeThreshold=4m 大对象设置为 4m

6.1.2. JVM参数

详细 JVM 配置参数如下


-verbose:gc
-Xmx40m
-Xms40m
-Xmn20m
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:PretenureSizeThreshold=4m
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=10
-XX:+PrintTenuringDistribution
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:gc.log

20220209100731

通过 JVM 配置,可以看到我们 tenured generation 大小为 40m - 20m = 20m

6.1.3. 代码示例


public class JvmGcBigObj {
    public static void main(String[] args) {
        byte[] bytes1 = new byte[4 * 1024 * 1024];
        bytes1 = new byte[4 * 1024 * 1024];
        bytes1 = new byte[4 * 1024 * 1024];
        byte[] bytes2 = new byte[128 * 1024];
        bytes2 = null;

        byte[] bytes3 = new byte[4 * 1024 * 1024];
    }
}

6.1.4. GC日志


Java HotSpot(TM) 64-Bit Server VM (25.301-b09) for windows-amd64 JRE (1.8.0_301-b09), built on Jun  9 2021 06:46:21 by "java_re" with MS VC++ 15.9 (VS2017)
Memory: 4k page, physical 33456676k(23420196k free), swap 54428196k(41646824k free)
CommandLine flags: -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=41943040 -XX:MaxNewSize=20971520 -XX:MaxTenuringThreshold=10 -XX:NewSize=20971520 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=4194304 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
Heap
 par new generation   total 18432K, used 3964K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,  24% used [0x00000000fd800000, 0x00000000fdbdf380, 0x00000000fe800000)
  from space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
  to   space 2048K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 16384K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3348K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 364K, capacity 388K, committed 512K, reserved 1048576K

6.1.5. GC分析

查看垃圾回收GC日志,发现并没有 Young GC ,通过 concurrent mark-sweep generation total 20480K, used 16384K ,我们可以看出来 一共 20M 的空间,直接被分配了 16M ,说明 bytes1bytes3 的内存申请都被放入 tenured generation 中,从而验证了结论。

6.2. 对象超过Survivor设定的大小

6.2.1. 场景假定

  • MaxTenuringThreshold 假定为 10
  • Xmn= 20M
  • XX:SurvivorRatio = 8, EdenSurvivor 比例 为 8

    • Eden = 16M
    • s0、s1 = 2M

6.2.2. JVM参数

详细 JVM 配置参数如下


-verbose:gc
-Xmx40m
-Xms40m
-Xmn20m
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=10
-XX:+PrintTenuringDistribution
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:gc-obj.log

6.2.3. JAVA示例

继续复用上述的 Demo 每个数组对象的大小为 4M


public class JvmObj {
    public static void main(String[] args) {
        byte[] bytes1 = new byte[4 * 1024 * 1024];
        bytes1 = new byte[4 * 1024 * 1024];
        bytes1 = new byte[4 * 1024 * 1024];
        byte[] bytes2 = new byte[128 * 1024];
        bytes2 = null;
        byte[] bytes3 = new byte[4 * 1024 * 1024];
        bytes3 = new byte[4 * 1024 * 1024];
        bytes3 = new byte[4 * 1024 * 1024];
        bytes3 = new byte[128 * 1024];
        bytes3 = null;

        byte[] bytes4 = new byte[4 * 1024 * 1024];
    }
}

6.2.4. GC日志

上述 JAVA 示例代码以及 JVM 参数配合起来运行,其会输出如下的 GC 日志。

Java HotSpot(TM) 64-Bit Server VM (25.301-b09) for windows-amd64 JRE (1.8.0_301-b09), built on Jun  9 2021 06:46:21 by "java_re" with MS VC++ 15.9 (VS2017)
Memory: 4k page, physical 33456676k(21280476k free), swap 54428196k(38983140k free)
CommandLine flags: -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=41943040 -XX:MaxNewSize=20971520 -XX:MaxTenuringThreshold=10 -XX:NewSize=20971520 -XX:OldPLABSize=16 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
{Heap before GC invocations=0 (full 0):
 par new generation   total 18432K, used 15924K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,  97% used [0x00000000fd800000, 0x00000000fe78d380, 0x00000000fe800000)
  from space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
  to   space 2048K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3340K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
2022-02-09T15:17:15.398+0800: 0.310: [GC (Allocation Failure) 2022-02-09T15:17:15.398+0800: 0.310: [ParNew
Desired survivor size 1048576 bytes, new threshold 1 (max 10)
- age   1:    1208816 bytes,    1208816 total
: 15924K->1348K(18432K), 0.0098407 secs] 15924K->5446K(38912K), 0.0100888 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap after GC invocations=1 (full 0):
 par new generation   total 18432K, used 1348K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,   0% used [0x00000000fd800000, 0x00000000fd800000, 0x00000000fe800000)
  from space 2048K,  65% used [0x00000000fea00000, 0x00000000feb51020, 0x00000000fec00000)
  to   space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
 concurrent mark-sweep generation total 20480K, used 4098K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3340K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=1 (full 0):
 par new generation   total 18432K, used 13956K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,  76% used [0x00000000fd800000, 0x00000000fe450278, 0x00000000fe800000)
  from space 2048K,  65% used [0x00000000fea00000, 0x00000000feb51020, 0x00000000fec00000)
  to   space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
 concurrent mark-sweep generation total 20480K, used 4098K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3342K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
2022-02-09T15:17:15.410+0800: 0.322: [GC (Allocation Failure) 2022-02-09T15:17:15.410+0800: 0.322: [ParNew
Desired survivor size 1048576 bytes, new threshold 10 (max 10)
: 13956K->0K(18432K), 0.0243044 secs] 18054K->5167K(38912K), 0.0244080 secs] [Times: user=0.66 sys=0.00, real=0.02 secs] 
Heap after GC invocations=2 (full 0):
 par new generation   total 18432K, used 0K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,   0% used [0x00000000fd800000, 0x00000000fd800000, 0x00000000fe800000)
  from space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
  to   space 2048K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 5167K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3342K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
}
Heap
 par new generation   total 18432K, used 4424K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,  27% used [0x00000000fd800000, 0x00000000fdc52040, 0x00000000fe800000)
  from space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
  to   space 2048K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 5167K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3349K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 364K, capacity 388K, committed 512K, reserved 1048576K

6.2.5. GC分析

  • main 方法开始连续创建 34MB 的数组,最后还把局部变量 bytes1 设置为了 null

byte[] bytes1 = new byte[4 * 1024 * 1024];
bytes1 = new byte[4 * 1024 * 1024];
bytes1 = new byte[4 * 1024 * 1024];
byte[] bytes2 = new byte[128 * 1024];
bytes2 = null;

所以此时的内存如下图所示:

20220209142617

  • 紧接着创建 bytes2 此时会在 Eden 创建 128kb 的数组对象

20220209142714


byte[] bytes2 = new byte[128 * 1024];
bytes2 = null;
  • 再创建 bytes3 的数组对象,当希望在 Eden 区再次分配一个 bytes3 的数组, Eden 空间已经不允许,没有足够的空间容量来存储 bytes3 对象, YOUNG GC 就在此刻被触发。

byte[] bytes3 = new byte[4 * 1024 * 1024];

2022-02-09T15:17:15.398+0800: 0.310: [GC (Allocation Failure) 2022-02-09T15:17:15.398+0800: 0.310: [ParNew
Desired survivor size 1048576 bytes, new threshold 1 (max 10)
- age   1:    1208816 bytes,    1208816 total
: 15924K->1348K(18432K), 0.0098407 secs] 15924K->5446K(38912K), 0.0100888 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

此时发生的第一次 Young GC : 15924K->1348K(18432K), 0.0098407 secs 通过 GC 日志分析,在 GC 之前年轻代占用空间 15924K ,包括 34M 的数组 、 1128k 的数组 ,还包含一些未知对象 3508k ,在垃圾回收后还存活的 未知对象大概是 1348K

20220211102508

在第一次 Young GC 将剩下 4MB 的数据和一些 未知对象 , 并不能全放入 from Survivor 区。因为 Survivor 区仅仅只有 2MB ,所以将 4MB 放入老年代, 未知对象 存入 Survivor


Heap after GC invocations=1 (full 0):
 par new generation   total 18432K, used 1348K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,   0% used [0x00000000fd800000, 0x00000000fd800000, 0x00000000fe800000)
  from space 2048K,  65% used [0x00000000fea00000, 0x00000000feb51020, 0x00000000fec00000)
  to   space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
 concurrent mark-sweep generation total 20480K, used 4098K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3340K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

20220211102602

  • 紧接着再分配 3个 4MB 的数组,然后再分配一个 128KB 的数组,最后是让 bytes3 变量指向 null,此时 Eden 已经有 3*1024+128 k 的对象,再分配 bytes4 ,已经无法容下,触发第二次 Young GC

20220211102838


        byte[] bytes3 = new byte[4 * 1024 * 1024];
        bytes3 = new byte[4 * 1024 * 1024];
        bytes3 = new byte[4 * 1024 * 1024];
        bytes3 = new byte[128 * 1024];
        bytes3 = null;
        byte[] bytes4 = new byte[4 * 1024 * 1024];

{Heap before GC invocations=1 (full 0):
 par new generation   total 18432K, used 13956K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,  76% used [0x00000000fd800000, 0x00000000fe450278, 0x00000000fe800000)
  from space 2048K,  65% used [0x00000000fea00000, 0x00000000feb51020, 0x00000000fec00000)
  to   space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
 concurrent mark-sweep generation total 20480K, used 4098K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3342K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
2022-02-09T15:17:15.410+0800: 0.322: [GC (Allocation Failure) 2022-02-09T15:17:15.410+0800: 0.322: [ParNew
Desired survivor size 1048576 bytes, new threshold 10 (max 10)
: 13956K->0K(18432K), 0.0243044 secs] 18054K->5167K(38912K), 0.0244080 secs] [Times: user=0.66 sys=0.00, real=0.02 secs] 
Heap after GC invocations=2 (full 0):
 par new generation   total 18432K, used 0K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,   0% used [0x00000000fd800000, 0x00000000fd800000, 0x00000000fe800000)
  from space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
  to   space 2048K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 5167K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3342K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
}

在第二次 Young GC 后, EdenSurvivor 的空间变为 0k ; 老年代的空间也由第一次GC 的 4098K -> 5167K ,老年代的对象包含 1个4M 的对象 和 一些 未知对象 ,其中未知对象约,这些未知对象从 Survivor 转移到 老年代 空间又被处理( HOTSPOT 具体对这些对象如何的有清楚的小伙伴麻烦告知下)。

20220211105401

  • 最终在第二次 Young GC 后, Eden 已经有空间对 bytes4 进行分配,效果图如下:

20220211105630

同时我们观察 GC日志也可以看到 Eden 空间占比已经是 27%,说明虚拟机确实按照这样的逻辑进行垃圾回收的。


Heap
 par new generation   total 18432K, used 4424K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 16384K,  27% used [0x00000000fd800000, 0x00000000fdc52040, 0x00000000fe800000)
  from space 2048K,   0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
  to   space 2048K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 5167K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3349K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 364K, capacity 388K, committed 512K, reserved 1048576K

7. 普通对象

7.1. 场景假定

  • MaxTenuringThreshold 假定为 3
  • Xmn= 18M
  • XX:SurvivorRatio = 1, EdenSurvivor 比例 为 1

    • Eden = 6144k
    • s0、s1 = 6144k

7.2. JVM参数


-verbose:gc
-Xmx38m
-Xms38m
-Xmn18m
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:SurvivorRatio=1
-XX:MaxTenuringThreshold=3
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-XX:+PrintHeapAtGC
-XX:+PrintReferenceGC
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
-Xloggc:gc-com.log

7.3. 代码示例


public class JvmSurviObj {

    public static void main(String[] args) {
        byte[] bytes1 = new byte[3 * 1024 * 1024];
        byte[] bytes2 = new byte[128 * 1024];
        bytes1 = null;
        byte[] bytes3 = new byte[3 * 1024 * 1024];
    }
}

7.4. GC日志


Java HotSpot(TM) 64-Bit Server VM (25.301-b09) for windows-amd64 JRE (1.8.0_301-b09), built on Jun  9 2021 06:46:21 by "java_re" with MS VC++ 15.9 (VS2017)
Memory: 4k page, physical 15965876k(4472588k free), swap 19504820k(6958176k free)
CommandLine flags: -XX:InitialHeapSize=39845888 -XX:InitialTenuringThreshold=3 -XX:MaxHeapSize=39845888 -XX:MaxNewSize=18874368 -XX:MaxTenuringThreshold=3 -XX:NewSize=18874368 -XX:OldPLABSize=16 -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintTenuringDistribution -XX:SurvivorRatio=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
{Heap before GC invocations=0 (full 0):
 par new generation   total 12288K, used 4380K [0x00000000fda00000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 6144K,  71% used [0x00000000fda00000, 0x00000000fde47180, 0x00000000fe000000)
  from space 6144K,   0% used [0x00000000fe000000, 0x00000000fe000000, 0x00000000fe600000)
  to   space 6144K,   0% used [0x00000000fe600000, 0x00000000fe600000, 0x00000000fec00000)
 concurrent mark-sweep generation total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2635K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 278K, capacity 386K, committed 512K, reserved 1048576K
2022-02-11T23:06:57.976+0800: 0.115: [GC (Allocation Failure) 2022-02-11T23:06:57.976+0800: 0.115: [ParNew2022-02-11T23:06:57.976+0800: 0.117: [SoftReference, 0 refs, 0.0000409 secs]2022-02-11T23:06:57.976+0800: 0.117: [WeakReference, 9 refs, 0.0000084 secs]2022-02-11T23:06:57.976+0800: 0.117: [FinalReference, 4 refs, 0.0000167 secs]2022-02-11T23:06:57.976+0800: 0.117: [PhantomReference, 0 refs, 0 refs, 0.0000076 secs]2022-02-11T23:06:57.976+0800: 0.117: [JNI Weak Reference, 0.0000066 secs]
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:     904344 bytes,     904344 total
: 4380K->934K(12288K), 0.0020749 secs] 4380K->934K(32768K), 0.0023095 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=1 (full 0):
 par new generation   total 12288K, used 934K [0x00000000fda00000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 6144K,   0% used [0x00000000fda00000, 0x00000000fda00000, 0x00000000fe000000)
  from space 6144K,  15% used [0x00000000fe600000, 0x00000000fe6e9b28, 0x00000000fec00000)
  to   space 6144K,   0% used [0x00000000fe000000, 0x00000000fe000000, 0x00000000fe600000)
 concurrent mark-sweep generation total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2635K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 278K, capacity 386K, committed 512K, reserved 1048576K
}
2022-02-11T23:06:57.976+0800: 0.117: Total time for which application threads were stopped: 0.0025014 seconds, Stopping threads took: 0.0000335 seconds
Heap
 par new generation   total 12288K, used 4068K [0x00000000fda00000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 6144K,  51% used [0x00000000fda00000, 0x00000000fdd0f748, 0x00000000fe000000)
  from space 6144K,  15% used [0x00000000fe600000, 0x00000000fe6e9b28, 0x00000000fec00000)
  to   space 6144K,   0% used [0x00000000fe000000, 0x00000000fe000000, 0x00000000fe600000)
 concurrent mark-sweep generation total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2642K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 279K, capacity 386K, committed 512K, reserved 1048576K

7.5. GC分析


        byte[] bytes1 = new byte[3 * 1024 * 1024];
        byte[] bytes2 = new byte[128 * 1024];
        bytes1 = null;

2022-02-11T23:06:57.976+0800: 0.115: [GC (Allocation Failure) 2022-02-11T23:06:57.976+0800: 0.115: [ParNew2022-02-11T23:06:57.976+0800: 0.117: [SoftReference, 0 refs, 0.0000409 secs]2022-02-11T23:06:57.976+0800: 0.117: [WeakReference, 9 refs, 0.0000084 secs]2022-02-11T23:06:57.976+0800: 0.117: [FinalReference, 4 refs, 0.0000167 secs]2022-02-11T23:06:57.976+0800: 0.117: [PhantomReference, 0 refs, 0 refs, 0.0000076 secs]2022-02-11T23:06:57.976+0800: 0.117: [JNI Weak Reference, 0.0000066 secs]
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:     904344 bytes,     904344 total
: 4380K->934K(12288K), 0.0020749 secs] 4380K->934K(32768K), 0.0023095 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

4380K->934K(12288K), 0.0020749 secs 中的 4380K 包含 1个3M bytes1 和 128k bytes2 ,以及将近 1180k 的未知对象。

  • 通过在第一次 Young GC 后, From Survivor15% 的新生代内存, 15% * 6144k = 921kb921k 包含 数组 128k 以及一些 未知对象eden 和 老年代 使用都 0%

20220212102548

  • 紧接着开始在 Eden 分配 bytes3 ,此时新生代空间为 3m 数组 加上 From Survivor921k ,最终空间分配如下:

20220212103009


Heap
 par new generation   total 12288K, used 4068K [0x00000000fda00000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 6144K,  51% used [0x00000000fda00000, 0x00000000fdd0f748, 0x00000000fe000000)
  from space 6144K,  15% used [0x00000000fe600000, 0x00000000fe6e9b28, 0x00000000fec00000)
  to   space 6144K,   0% used [0x00000000fe000000, 0x00000000fe000000, 0x00000000fe600000)
 concurrent mark-sweep generation total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2642K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 279K, capacity 386K, committed 512K, reserved 1048576K

8. 动态年龄

8.1. 场景假定

  • MaxTenuringThreshold 假定为 3
  • Xmn= 18M
  • XX:SurvivorRatio = 1, EdenSurvivor 比例 为 1

    • Eden = 6144k
    • s0、s1 = 6144k

8.2. JVM参数


-verbose:gc
-Xmx38m
-Xms38m
-Xmn18m
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:SurvivorRatio=1
-XX:MaxTenuringThreshold=3
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-XX:+PrintHeapAtGC
-XX:+PrintReferenceGC
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
-Xloggc:gc-com.log

8.3. 代码示例


public class JvmSurviObj {

    public static void main(String[] args) {
        byte[] bytes1 = new byte[3 * 1024 * 1024];
        byte[] bytes2 = new byte[128 * 1024];
        bytes1 = null;
        byte[] bytes3 = new byte[3 * 1024 * 1024];
    }
}

8.4. GC日志

8.5. GC分析

20220209171053

From Survivor 内对象的年龄已经是 1,而此时 Survivor 区域总大小是 2MB,此时 Survivor 区域中的存活对象已经有 1331k ,绝对超过 50%。触发第二次 Young GC,然后看看 Survivor 区域内的动态年龄判定规则能否生效。

  • 最后执行 byte[] bytes4 = new byte[4 * 1024 * 1024]Eden 区如果要再次放一个 4MB 数组下去,是放不下去的了,所以会再触发一次 Young GC

2022-01-27T16:47:32.378+0800: 0.302: [GC (Allocation Failure) 2022-01-27T16:47:32.378+0800: 0.302: [ParNew
Desired survivor size 1048576 bytes, new threshold 10 (max 10)
: 14139K->0K(18432K), 0.0237425 secs] 14139K->1199K(38912K), 0.0238285 secs] [Times: user=0.05 sys=0.02, real=0.02 secs] 
  • 最终 GC 日志如下,此刻 14139K->0K(18432K), 0.0237425 secs 说明年轻代已经没有对象存在,但是此前年轻代还有未知对象。

20220127170535

Eden 在第二次 GC 后,由于都没引用,所以都被回收走了,只剩下 Survivor 区域中的对象,而且总大小超过 50%了,而且年龄都是1岁。

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

20220127171207


concurrent mark-sweep generation total 20480K, used 1199K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  • CMS 管理的老年代,此时使用空间刚是 1199K ,此时 Survivor 里的对象触发动态年龄判定规则,虽然没有达到10岁,但是全部进入老年代了,包括:

    • bytes2 变量一直引用的 128KB 数组
    • bytes4 变量引用的那个 4MB 数组,此时就会分配到 Eden 区域中

最终内存分布如图:

20220127172316

8.6. TargetSurvivorRatio 分析


uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
    //survivor_capacity是survivor空间的大小
  size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
  size_t total = 0;
  uint age = 1;
  while (age < table_size) {
    total += sizes[age];//sizes数组是每个年龄段对象大小
    if (total > desired_survivor_size) break;
    age++;
  }
  uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
    ...
}

TargetSurvivorRatio 这个数值默认为 50% 通过它可以完成以下四项:

  • 计算一个期望值,desired_survivor_size
  • 然后用一个 total 计数器,累加每个年龄段对象大小的总和。
  • total 大于 desired_survivor_size 停止。
  • 然后用当前叠加的年龄和 设定 MaxTenuringThreshold 对比找出最小值作为结果

综述,动态年龄计算规则就是将年龄按照从小到大进行容量累加,当加入某个年龄段后,累加超过 Survivor 区域 TargetSurvivorRatio,就从这个年龄段向上的年龄的对象进行晋升。

https://blog.csdn.net/weixin_42405670/article/details/120426799

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
6月前
|
监控 Java
jvm性能调优实战 - 24模拟因动态年龄判断对象进入老年代的场景
jvm性能调优实战 - 24模拟因动态年龄判断对象进入老年代的场景
98 0
|
6月前
|
Java
堆内存的溢出案例分析
堆内存的溢出案例分析
25 0
|
6月前
|
存储 算法 安全
深度解析JVM世界:JVM内存分配
深度解析JVM世界:JVM内存分配
|
6月前
|
算法 Oracle Java
【JVM】了解JVM中动态判断对象年龄的原理
【JVM】了解JVM中动态判断对象年龄的原理
136 0
|
算法 安全 Java
18-动态对象年龄判断+空间分配担保规则+老年代回收算法
本文中用到的案例是接着上一篇文章继续的,如果有不清楚同学请先查看上一篇文章
141 0
 18-动态对象年龄判断+空间分配担保规则+老年代回收算法
|
存储 算法 Java
JVM 收集算法 垃圾收集器 元空间 引用
JVM 收集算法 垃圾收集器 元空间 引用
104 0
|
存储 Java 编译器
Java内存区域介绍以及JDK1.8内存变化
Java内存区域介绍以及JDK1.8内存变化
318 0
Java内存区域介绍以及JDK1.8内存变化
|
存储 Java 程序员
【JVM】方法区与永久代、元空间之间的关系
【JVM】方法区与永久代、元空间之间的关系
194 0
|
存储 Java
JVM - 结合代码示例彻底搞懂Java内存区域_对象在堆-栈-方法区(元空间)之间的关系
JVM - 结合代码示例彻底搞懂Java内存区域_对象在堆-栈-方法区(元空间)之间的关系
122 0
|
Oracle 安全 Java
JVM将初始和最大内存大小设置为相同值的好处
JVM将初始和最大内存大小设置为相同值的好处
1211 1