title: Hotspot动态年龄计算
date: 2022-01-29 15:00:00
categories: Java
description: JVM 调优
1. 目录
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
= 20MXX:SurvivorRatio
= 8,Eden
和Survivor
比例 为8
Eden
= 16Ms0、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
通过 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
,说明 bytes1
和 bytes3
的内存申请都被放入 tenured generation
中,从而验证了结论。
6.2. 对象超过Survivor设定的大小
6.2.1. 场景假定
MaxTenuringThreshold
假定为10
Xmn
= 20MXX:SurvivorRatio
= 8,Eden
和Survivor
比例 为8
Eden
= 16Ms0、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
方法开始连续创建3
个4MB
的数组,最后还把局部变量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;
所以此时的内存如下图所示:
- 紧接着创建
bytes2
此时会在Eden
创建 128kb 的数组对象
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
,包括 3
个 4M
的数组 、 1
个 128k
的数组 ,还包含一些未知对象 3508k
,在垃圾回收后还存活的 未知对象
大概是 1348K
。
在第一次 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
- 紧接着再分配 3个 4MB 的数组,然后再分配一个
128KB
的数组,最后是让bytes3
变量指向null
,此时Eden
已经有3*1024+128
k 的对象,再分配bytes4
,已经无法容下,触发第二次Young GC
。
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
后, Eden
和 Survivor
的空间变为 0k
; 老年代的空间也由第一次GC 的 4098K -> 5167K
,老年代的对象包含 1个4M 的对象 和 一些 未知对象
,其中未知对象约,这些未知对象从 Survivor
转移到 老年代 空间又被处理( HOTSPOT
具体对这些对象如何的有清楚的小伙伴麻烦告知下)。
- 最终在第二次
Young GC
后,Eden
已经有空间对bytes4
进行分配,效果图如下:
同时我们观察 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
= 18MXX:SurvivorRatio
= 1,Eden
和Survivor
比例 为1
Eden
= 6144ks0、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 Survivor
占15%
的新生代内存,15% * 6144k = 921kb
,921k
包含 数组128k
以及一些未知对象
;eden
和 老年代 使用都0%
。
- 紧接着开始在
Eden
分配bytes3
,此时新生代空间为3m
数组 加上From Survivor
的921k
,最终空间分配如下:
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
= 18MXX:SurvivorRatio
= 1,Eden
和Survivor
比例 为1
Eden
= 6144ks0、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分析
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
说明年轻代已经没有对象存在,但是此前年轻代还有未知对象。
Eden
在第二次 GC
后,由于都没引用,所以都被回收走了,只剩下 Survivor
区域中的对象,而且总大小超过 50%了,而且年龄都是1岁。
根据 Hotspot
对动态年龄判定规则: 年龄1 + 年龄2 + 年龄n 的对象总大小超过了 Survivor
区域的 50%,年龄n 以上的对象进入老年代。
concurrent mark-sweep generation total 20480K, used 1199K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
CMS
管理的老年代,此时使用空间刚是1199K
,此时Survivor
里的对象触发动态年龄判定规则,虽然没有达到10岁,但是全部进入老年代了,包括:bytes2
变量一直引用的 128KB 数组bytes4
变量引用的那个 4MB 数组,此时就会分配到Eden
区域中
最终内存分布如图:
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