手把手教你如何写出完美的JVM的Young GC

简介: 模拟JVM的Young GCJVM参数示范(基于JDK 1.8)

模拟JVM的Young GC


JVM参数示范(基于JDK 1.8)

用如下JVM参数运行代码:


# 初始新生代大小 5M

-XX:NewSize=5242880

# 最大新生代大小 5M

-XX:MaxNewSize=5242880

# 初始堆大小 10M

-XX:InitialHeapSize=10485760

# 最大堆大小 10M

-XX:MaxHeapSize=10485760

-XX:SurvivorRatio=8

# 大对象阈值是10MB

-XX:PretenureSizeThreshold=10485760

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC


17.png


如何打印JVM GC日志?

GC日志打印选型:


-XX:+PrintGCDetils:打印详细gc日志

-XX:+PrintGCTimeStamps:这个参数可以打印出来每次GC发生的时间

-Xloggc:gc.log:这个参数可以设置将gc日志写入一个磁盘文件


加上该参数后,JVM参数如下:


-XX:NewSize=5242880

-XX:MaxNewSize=5242880

-XX:InitialHeapSize=10485760

-XX:MaxHeapSize=10485760

-XX:SurvivorRatio=8

-XX:PretenureSizeThreshold=10485760

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:gc.log


实例

16.png


对象是如何分配在Eden

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

1

该行会在JVM Eden内放入一个1M数组,同时在main线程的虚拟机栈压入一个main方法栈帧,其栈帧内部有一“arr1”变量,该变量指向Eden的那1M数组:

15.png



arr1 = new byte[1024 * 1024];


此时会在Eden创建第二个数组,局部变量指向其。然后第一个数组无人引用,成了垃圾:

14.png



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


在Eden创建第三个数组,同时让arr1指向第三个数组,此时前两个数组都无人引用,都成了垃圾:


13.png


arr1 = null;


arr1啥都不指了,导致之前创建的3数组全部变成垃圾:




byte[] arr2 = new byte[2 * 1024 * 1024];


分配一个2MB大小的数组,尝试放入Eden,这时Eden放的下吗?


显然不行,Eden共4M,已放入3个1M数组,只剩1M,所以这时就会触发Y-GC。

采用指定JVM参数运行程序

12.png


然后运行即可,运行完后,会出现gc.log文件,即本次程序运行的gc日志:

11.png



打开gc.log文件,我们会看到如下所示的gc日志:


10.png9.png




这次运行程序的JVM参数,包含:


我们自己手动设置的


默认的参数设置


给JVM加个打印gc日志的参数,就可以在此看到JVM默认参数配置


一次GC的路途


0.410: [GC (Allocation Failure) 0.410: [ParNew: 3863K->465K(4608K), 0.0025689 secs] 3863K->1491K(9728K), 0.0029347 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]


0.269: [ParNew: 4030K->512K(4608K), 0.0015734 secs] 4030K->574K(9728K), 0.0017518 secs]


[Times: user=0.00 sys=0.00, real=0.00 secs]


概要说明本次GC执行情况:


GC (Allocation Failure),看字面意思即可,为啥会发生一次GC呢?


因为要分配一个2M数组,但Eden内存不足,所以“Allocation Failure”,内存分配失败,所以就要触发一次Y-GC。


那这次GC何时发生?

看一个数字,“0.410”:你的系统运行后,过了多少s发生的本次GC,此处就是系统运行后的410ms发生了GC。


ParNew: 3863K->465K(4608K), 0.0025689 secs

此处触发的是Y-GC,所以用的也就是指定的ParNew来执行GC。


3863K->465K(4608K)

新生代可用空间4608KB,约4.5MB,Eden是4M,两个Survivor只有一个可放存活对象,另外一个必须保持空闲,所以年轻代可用空间是 Eden+1个Survivor=4.5MB。


3863K->465K:对年轻代执行一次GC,GC前使用了3863K,但GC后只有465K对象存活。


GC前,我们在Eden只放了3个1M数组,共3MB,即3072K,那为啥这里显示使用了3863K?


自定义创建的数组本身虽是1MB,但为存储该数组,JVM内置还会附带一些其它信息,所以每个数组实际占用的内存是大于1MB的

可能还有一些你看不见的对象在Eden里

所以GC前,三个数组+其他未知对象=3863K内存。GC前,我们在Eden只放了3个1M数组,共3MB,即3072K,那为啥这里显示使用了3863K?


自定义创建的数组本身虽是1MB,但为存储该数组,JVM内置还会附带一些其它信息,所以每个数组实际占用的内存是大于1MB的

可能还有一些你看不见的对象在Eden里

所以GC前,三个数组+其他未知对象=3863K内存。


0.0025689 secs:本次GC耗费时间,看这里来说大概耗费2.5ms,仅回收3MB对象而已。


3863K->1491K(9728K), 0.0029347 secs

整个Java堆内存的情况。整个Java堆内存总可用空间9728K(9.5M)=年轻代4.5MB+老年代5M。


GC前,整个Java堆内存使用了3863K

GC后Java堆内存使用了1491K

[Times: user=0.01 sys=0.00, real=0.01 secs]


本次gc消耗的时间,这里最小单位是小数点之后两位,单位是s。


图解GC执行过程


ParNew执行GC,回收掉自定义创建的三个数组,此时因为他们都无人引用,必成垃圾:

8.png



ParNew: 4030K->512K(4608K), 0.0015734 secs


gc回收后,从4030K内存使用降低到512K内存使用。即这次gc日志有512KB的对象存活,从Eden区转移到Survivor1区:

7.png



GC后的堆内存


Heap

par new generation total 4608K, used 2601K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000) eden space 4096K, 51% used [0x00000000ff600000, 0x00000000ff80a558, 0x00000000ffa00000)

from space 512K, 100% used [0x00000000ffa80000, 0x00000000ffb00000, 0x00000000ffb00000) to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)

concurrent mark-sweep generation total 5120K, used 62K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)




Metaspace    used 2782K, capacity 4486K, committed 4864K, reserved 1056768K class space used 300K, capacity 386K, committed 512K, reserved 1048576K

JVM退出时打印的当前堆内存使用情况



ParNew负责的新生代共4608K(4.5MB)可用内存,目前使用2630K(2.5MB)。此时在JVM退出前,为何新生代占了2.5M?GC后,通过如下代码又分配了个2M数组:


byte[] arr2 = new byte[2 * 1024 * 1024];


所以此时在Eden一定会有个 2M数组=2048K,然后上次GC后,在From Survivor存活了个512K未知对象,那么:


2048 K B + 512 K B = 2560 K B 2048KB + 512KB = 2560KB2048KB+512KB=2560KB


每个数组会额外占据一些内存存放一些自己这个对象的元数据,可认为多出来的70K是数组对象额外使用的内存空间。

6.png


Eden日志

Eden此时4M内存被用52%,就因为那2M数组。然后From Survivor区,512K是100%使用率,此时被之前GC后存活下来的512K未知对象占据。


CMS、Metaspace元数据空间和Class空间

Concurrent Mark-Sweep垃圾回收器,即CMS管理的老年代内存空间共5M,此时使用了62K。

Metaspace元数据空间和Class空间,存放一些类信息、常量池之类的东西,此时他们的总容量,使用内存等。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
7天前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
33 3
|
13天前
|
运维 Java Shell
手工触发Full GC:JVM调优实战指南
本文是关于Java应用性能调优的指南,重点介绍了如何使用`jmap`工具手动触发Full GC。Full GC是对堆内存全面清理的过程,通常在资源紧张时进行以缓解内存压力。文章详细阐述了Full GC的概念,并提供了两种使用`jmap`触发Full GC的方法:通过`-histo:live`选项获取存活对象统计信息,或使用`-dump`选项生成堆转储文件以分析内存状态。同时,文中也提醒注意手动Full GC可能带来的性能开销,建议在生产环境中谨慎操作。
|
1月前
|
存储 算法 Java
JVM(垃圾回收机制 --- GC)
JVM(垃圾回收机制 --- GC)
42 5
|
1月前
|
Java 程序员 Python
JVM的垃圾回收机制(GC机制)
Java的JVM实行自动垃圾回收机制(GC),主要针对堆中的对象。当对象无引用可达时,被视为垃圾。垃圾回收包含“找垃圾”和“回收垃圾”两步。找垃圾通过引用计数(非Java使用)和可达性分析(Java使用)来识别无用对象。可达性分析从根对象开始遍历,未被标记的对象视为垃圾。回收垃圾常用标记清除方法,但可能导致内存碎片。此过程消耗资源,且碎片化影响内存分配效率。
24 1
|
1月前
|
监控 Java Linux
JVM工作原理与实战(三十七):Shenandoah GC和ZGC
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收器的技术演进、Shenandoah GC、ZGC等内容。
32 0
|
1月前
|
存储 缓存 监控
JVM工作原理与实战(三十四):解决GC问题的方法
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了常见的垃圾回收(GC)模式、解决GC问题的方法(优化基础JVM参数、减少对象产生、更换垃圾回收器、优化垃圾回收器的参数)等内容。
38 0
|
1月前
|
Prometheus 监控 Cloud Native
JVM工作原理与实战(三十三):监控GC过程的工具
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了jstat工具、VisualVM插件、Prometheus + Grafana、GC日志等内容。
41 0
|
1月前
|
监控 负载均衡 算法
JVM工作原理与实战(三十二):GC调优
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了GC调优、GC调优的核心指标等内容。
26 0
|
1月前
|
存储 算法 Java
精华推荐 | 【JVM深层系列】「GC底层调优专题」一文带你彻底加强夯实底层原理之GC垃圾回收技术的分析指南(GC原理透析)
精华推荐 | 【JVM深层系列】「GC底层调优专题」一文带你彻底加强夯实底层原理之GC垃圾回收技术的分析指南(GC原理透析)
68 0
|
1月前
|
算法 Java
深入理解JVM - 解读GC日志
深入理解JVM - 解读GC日志
64 0