JVM优化案例实战-手动模拟Young GC

发布于:2022-12-13 ⋅ 阅读:(190) ⋅ 点赞:(0)

手动模拟发生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的老年代、元空间等的内存使用情况。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到