JVM优化案例实战-手动模拟Young GC
精选
原创
©著作权归作者所有:来自51CTO博客作者小二上酒8的原创作品,请联系作者获取转载授权,否则将追究法律责任
手动模拟发生Young GC
本文将通过设置固定的堆内存、新生代等内存空间大小,写代码去手动触发YoungGC,然后根据打印出的GC log日志去一步一步剖析整个流程。
我们先来设置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
这些参数的解释如下:
- XX:NewSize和XX:MaxNewSize初始新生代和最大新生代的大小,为5MB
- XX:InitialHeapSize和XX:MaxHeapSize:初始堆内存大小和最大堆内存大小,为10MB
- XX:SurvivorRatio:新生代中Eden区和Survivor区的大小比例,8代表Eden区占整个新生代区域的80%
- XX:PretenureSizeThreshold:指定大对象的阈值是10MB
- UseParNewGC新生代用的是ParNewGC垃圾回收器
- XX:+UseConcMarkSweepGC老年代用的是CMS垃圾回收器
- XX:+PrintGCDetails:打印详细的GC日志
- XX:+PrintGCTimeStamps:打印出每次GC发生的时间
- Xloggc:gc.log:将GC日志写入gc.log文件中
相当于给堆内存分配了10MB空间,新生代大小为5MB,其中Eden区占4MB,两个Survivor区分别占0.5MB();老年代大小为5MB,大对象必须超过10MB才直接进入老年代
模拟代码:
public static void main(String[] args) {
byte[] array1 = new byte[1024 * 1024]; //1MB
array1 = new byte[1024 * 1024];//1MB
array1 = new byte[1024 * 1024];//1MB
array1 = null;
byte[] array2 = new byte[2 * 1024 * 1024];
}
代码解析:
- 代码行1:byte[] array1 = new byte[1024 * 1024]; 在新生代Eden区分配了1MB的空间,用来存储数组:new byte[1024 * 1024],并且在main方法栈内,array1指针指向该数组对象
\
\
- 代码行2:array1 = new byte[1024 * 1024]; 在新生代Eden区又分配了1MB的空间,用来存储数组:new byte[1024 * 1024],并且array1指向新的数组,原来的数组成为了垃圾对象
\
- 代码行3:array1 = new byte[1024 * 1024]; 又重新分配了一个数组空间,并且array1指向最新的数组对象,此时Eden区内有两个垃圾对象
\
- 代码行4:array1 = null; array1不指向任何对象,则之前分配的三个数组对象都成为垃圾对象
- 代码行5:byte[] array2 = new byte[2 * 1024 * 1024]; 在Eden区内分配2MB的空间。但是由于之前在Eden区已经有三个对象,占用了3MB空间,Eden本身只有4MB,需要再分配2MB很明显空间不够,会触发Young GC
执行代码后,会在目录下生成gc.log日志文件,我们可以根据这个文件查看GC回收的具体细节,这是我们做JVM调优的基础。
GC日志解析
GC日志如下:
Java HotSpot(TM) 64-Bit Server VM (25.321-b07) for bsd-amd64 JRE (1.8.0_321-b07), built on Dec 15 2021 19:12:29 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.17)
Memory: 4k page, physical 16777216k(45636k free)
/proc/meminfo: CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3596K->420K(4608K), 0.0047305 secs] 3596K->1446K(9728K), 0.0053325 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] Heap
par new generation total 4608K, used 3642K [0x00000007bf600000, 0x00000007bfb00000, 0x00000007bfb00000) eden space 4096K, 78% used [0x00000007bf600000, 0x00000007bf925a40, 0x00000007bfa00000) from space 512K, 82% used [0x00000007bfa80000, 0x00000007bfae9048, 0x00000007bfb00000) to space 512K, 0% used [0x00000007bfa00000, 0x00000007bfa00000, 0x00000007bfa80000) concurrent mark-sweep generation total 5120K, used 1026K [0x00000007bfb00000, 0x00000007c0000000, 0x00000007c0000000) Metaspace used 3216K, capacity 4496K, committed 4864K, reserved 1056768K class space used 358K, capacity 388K, committed 512K, reserved 1048576K
下面我们就来分析下GC日志。
CommandLine flags
本次代码执行的JVM参数,其中有我们设置的JVM参数,也有系统默认的设置参数
Young GC:
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3596K->420K(4608K), 0.0047305 secs] 3596K->1446K(9728K), 0.0053325 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
这一行是我们需要分析的重点。
0.125: [GC (Allocation Failure) 0.125 表示在系统运行0.125S后,内存分配失败,为什么会内存分配失败呢?看我们之前的代码,Eden区总的4MB空间,之前已经有3个垃圾对象,最后需要分配2MB的空间,就会发生内存不够,因此分配失败,此时就会触发一次Young GC,
[ParNew: 3596K->420K(4608K), 0.0047305 secs]
ParNew表示使用的是年轻代的ParNew 垃圾回收器来进行垃圾回收,后面的3596K->420K表示,回收之前的新生代大小为3.5MB,回收之后为420K约等于0.5MB,相当于本次回收大概回收了3MB的垃圾对象。0.0047305 secs表示本次GC回收耗时。
新生代区的总可用大小为4MB+0.5MB=4.5MB,4.5MB呢就是Eden区大小+一个Survivor区的大小,因为两个Survivor区一个需要用来存放存活对象,另一个必须保持空闲,所以总可用大小包括了Eden区大小+空闲Survivor区大小。
3596K->1446K(9728K), 0.0053325 secs]表示整个堆内存空间的使用情况,总的大小为9728K也就是9.5MB,为什么不是10MB呢 ?其实就是去除了一个Survivor区域的内存空间。在进行GC回收之前堆内存空间是3596K,回收后是1446K。
Heap:
Heap
par new generation total 4608K, used 3642K [0x00000007bf600000, 0x00000007bfb00000, 0x00000007bfb00000)
eden space 4096K, 78% used [0x00000007bf600000, 0x00000007bf925a40, 0x00000007bfa00000)
from space 512K, 82% used [0x00000007bfa80000, 0x00000007bfae9048, 0x00000007bfb00000)
to space 512K, 0% used [0x00000007bfa00000, 0x00000007bfa00000, 0x00000007bfa80000)
concurrent mark-sweep generation total 5120K, used 1026K [0x00000007bfb00000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 3216K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 358K, capacity 388K, committed 512K, reserved 1048576K
这些日志是在JVM退出前打印出的当时JVM堆内存使用的情况。
包括使用ParNew GC的新生代、Eden、From Survivor区、To Survivor区、使用CMS的老年代、元空间等的内存使用情况。