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

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
9月前
|
人工智能 自然语言处理 API
Hologres × PAI × DeepSeek 搭建 RAG 检索增强对话系统
本文介绍如何使用PAI-EAS部署基于DeepSeek大模型的RAG(检索增强生成)服务,并关联Hologres引擎实例。Hologres与阿里云自研高性能向量计算软件库Proxima深度整合,支持高性能、低延时的向量计算能力。通过PAI-EAS,用户可以一键部署集成了大语言模型和RAG技术的对话系统服务,显著缩短部署时间,并提高问答质量。部署步骤包括准备Hologres向量检索库、部署基于DeepSeek的RAG服务、通过WebUI进行模型推理验证,以及通过API调用进行模型推理验证。Hologres还提供了特色功能支持,如高性能向量计算等。
|
Linux Windows
IDEA如何查看所有的断点(Breakpoints)并关闭
【10月更文挑战第15天】在 IntelliJ IDEA 中,可以通过以下步骤查看和关闭所有断点: 1. 查看所有断点: - 打开断点窗口:菜单栏选择 “View” -&gt; “Tool Windows” -&gt; “Debug”,或使用快捷键 “Alt+2”(Windows/Linux)/“Command+2”(Mac)。 - 在断点窗口中,可以看到所有设置的断点列表,包括文件、行号等信息。 2. **关闭断点**: - 单个断点关闭:在断点窗口中,点击断点左侧的红点图标即可关闭。
5433 2
|
存储 Java Linux
Java“Bad Magic Number”错误解决
Java“Bad Magic Number”错误通常发生在尝试运行不兼容或损坏的类文件时。解决方法包括确保使用正确的JDK版本、检查类文件完整性、清理和重新编译项目。
754 14
|
机器学习/深度学习 人工智能 算法
人工智能伦理:当机器拥有道德判断力
随着人工智能技术的快速发展,AI已经从简单的任务执行者逐渐转变为具有决策能力的智能体。本文将探讨AI在伦理道德方面的挑战与机遇,分析AI如何通过算法模拟人类道德判断,以及这一进步可能给社会带来的深远影响。我们将深入讨论AI伦理决策的实现路径、面临的技术难题和未来发展趋势,同时评估其对法律、社会结构和个人隐私的潜在影响。文章旨在为读者提供一个关于AI伦理决策的全面视角,并引发对于科技发展与人类价值观之间关系的深思。
445 27
|
存储 监控 数据安全/隐私保护
灾备计划的设计与实施:构建企业数据安全的坚固防线
【8月更文挑战第16天】灾备计划的设计与实施是一个复杂而持续的过程,需要企业高层的重视和全体员工的参与。通过科学的风险评估、合理的策略制定、完善的流程与文档、以及有效的实施与持续优化,企业可以构建起一套高效、可靠的灾备体系,为业务连续性和数据安全提供坚实保障。在数字化转型的浪潮中,灾备计划已成为企业不可或缺的战略资产。
|
Ubuntu
简单几步实现Ubuntu22.04启用Nvidia显卡
本文是关于如何在Ubuntu 22.04操作系统上启用Nvidia显卡的教程,包括禁用旧驱动、添加新驱动源、安装推荐驱动、重启系统以及通过Nvidia设置更改为高性能模式的步骤。
3860 0
简单几步实现Ubuntu22.04启用Nvidia显卡
GitHub爆赞!终于有大佬把《Python学习手册》学习笔记分享出来了
这份笔记的目标是为了给出一份比较精炼,但是又要浅显易懂的Python教程。《Python学习手册》中文第四版虽然比较简单,但是措辞比较罗嗦,而且一个语法点往往散落在多个章节,不方便读者总结。 我在做笔记时,将一个知识点的内容都统筹在一个章节里面,因此提炼性大大提高。而且还有《Python学习手册》中文第四版的翻译在某些章节(可能难度较大?)措辞可能前后矛盾。当知识点提炼之后就能够很快的找到一些难以理解的概念的上下文,方便吃透这些难点。
|
算法 Java 数据库
Java CompletableFuture.runAsync的概念于实战
【4月更文挑战第1天】在Java中,CompletableFuture.runAsync是CompletableFuture类中的一个静态方法,用于异步执行不返回结果的任务。这使得它成为处理并发编程任务时的一个非常有用的工具,特别是在开发需要非阻塞操作的应用程序时。
1211 3
|
关系型数据库 MySQL 数据库
INSERT IGNORE与INSERT INTO的区别
INSERT IGNORE与INSERT INTO的区别
498 0
|
网络虚拟化 网络架构
配置基于接口划分VLAN示例(汇聚层设备作为网关)
划分VLAN的方式有:基于接口、基于MAC地址、基于IP子网、基于协议、基于策略(MAC地址、IP地址、接口)。其中基于接口划分VLAN,是最简单,最常见的划分方式。 基于接口划分VLAN指的是根据交换机的接口来划分VLAN。网络管理员预先给交换机的每个接口配置不同的PVID,当一个数据帧进入交换机时,如果没有带VLAN标签,该数据帧就会被打上接口指定PVID的Tag,然后数据帧将在指定PVID中传输。 在典型的分层组网中,当接入交换机是二层交换机时,可以使用汇聚交换机作为用户的网关。另外使用汇聚交换机作为用户的网关还可以简化接入交换机的配置,使用户通过一个出接口访问外部网络,便于维护和管
567 0

热门文章

最新文章