JDK8中常用如下的垃圾收集器,它们分别运用在年轻代和老年代:
ParNew : 年轻代垃圾收集器,多线程,采用标记—复制算法。
CMS:老年代的收集器,全称(Concurrent Mark and Sweep),是一种以获取最短回收停顿时间为目标的收集器。
可以通过如下JVM参数进行配置:
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
一、ParNew
意为“Parallel New Generation”,需要说明的是, ParNew虽然是个minor gc,但它会Stop-The-World。因此ParNew间隔时间越长越好,并且每次执行的时间越短越好。下面是一次ParNew打印的日志
[GC (Allocation Failure) [ParNew: 367523K->1293K(410432K), 0.0023988 secs] 522739K->156516K(1322496K), 0.0025301 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]
其中的时间分别表示如下:
- 用户态消耗的CPU时间
- 内核态消耗的CPU事件
- 操作从开始到结束所经过的墙钟时间(Wall Clock Time)
这里需要说明一下CPU时间与墙钟时间的区别:
墙钟时间包括各种非运算的等待耗时,例如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以读者看到user或sys时间超过real时间是完全正常的。
因此在查看GC日志时,主要关注real对应的时间。
二、CMS
意为“Concurrent Mark Sweep”,CMS主要分为如下几个阶段
- CMS-initial-mark # 需要Stop the word
- CMS-concurrent-mark
- CMS-concurrent-preclean
- CMS-concurrent-abortable-preclean
- CMS-remark # 需要Stop the word
- CMS-concurrent-sweep
- CMS-initial-mark
这里需要特别关注CMS-initial-mark和CMS-remark这2个阶段,因为这2个阶段需要Stop-The-World。对于CMS来说,也是间隔时间越长越好,每次执行时间越短越好。
关于这几个阶段GC日志的具体内容,下面是一个例子。
2024-10-23T02:00:02.962+0800: 6484013.043: [GC (CMS Initial Mark) [1 CMS-initial-mark: 3237844K(3512768K)] 3309241K(4126208K), 0.0086396 secs] [Times: user=0.05 sys=0.00, real=0.00 secs]
2024-10-23T02:00:02.971+0800: 6484013.052: [CMS-concurrent-mark-start]
2024-10-23T02:00:03.137+0800: 6484013.217: [CMS-concurrent-mark: 0.165/0.165 secs] [Times: user=0.51 sys=0.00, real=0.16 secs]
2024-10-23T02:00:03.137+0800: 6484013.218: [CMS-concurrent-preclean-start]
2024-10-23T02:00:03.144+0800: 6484013.225: [CMS-concurrent-preclean: 0.007/0.008 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
2024-10-23T02:00:03.145+0800: 6484013.225: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time 2024-10-23T02:00:08.260+0800: 6484018.341: [CMS-concurrent-abortable-preclean: 5.072/5.116 secs] [Times: user=10.61 sys=0.05, real=5.11 secs]
2024-10-23T02:00:08.266+0800: 6484018.347: [GC (CMS Final Remark) [YG occupancy: 387736 K (613440 K)]
2024-10-23T02:00:08.266+0800: 6484018.347: [Rescan (parallel) , 0.0438231 secs]
2024-10-23T02:00:08.310+0800: 6484018.391: [weak refs processing, 0.0087443 secs]
2024-10-23T02:00:08.319+0800: 6484018.400: [class unloading, 0.2055055 secs]
2024-10-23T02:00:08.524+0800: 6484018.605: [scrub symbol table, 0.0716909 secs]
2024-10-23T02:00:08.596+0800: 6484018.677: [scrub string table, 0.0047778 secs][1 CMS-remark: 3237844K(3512768K)] 3625580K(4126208K), 0.3666275 secs] [Times: user=0.67 sys=0.00, real=0.37 secs]
2024-10-23T02:00:08.633+0800: 6484018.714: [CMS-concurrent-sweep-start]
2024-10-23T02:00:11.761+0800: 6484021.842: [CMS-concurrent-sweep: 3.112/3.128 secs] [Times: user=6.59 sys=0.06, real=3.13 secs]
2024-10-23T02:00:11.761+0800: 6484021.842: [CMS-concurrent-reset-start]
2024-10-23T02:00:11.771+0800: 6484021.851: [CMS-concurrent-reset: 0.009/0.009 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
另外,关于CMS需要关注2个JVM参数:
-XX:CMSInitiatingOccupancyFraction
-XX:+UseCMSInitiatingOccupancyOnly
1、-XX:CMSInitiatingOccupancyFraction
老年代堆占用了多大比例时,会做一次CMS。默认值是-1,表示不启用。大于等于0则直接取其值,小于0则根据如下公式来计算:
((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0
CMSTriggerRatio在JDK1.8是80
MinHeapFreeRatio在JDK1.8是40
2、-XX:+UseCMSInitiatingOccupancyOnly
是否一直使用上述1中设定的阈值,如果不指定,JVM仅在第一次使用设定值,后续则自动调整。
三、DefNew
意为“Default New Generation”,新生代Serial收集器中。由于用的少,就不介绍了。它打印的GC日志大概是这样的。
[DefNew: 78656K->78656K(78656K), 0.0000398 secs]
四、一些重要的JVM参数
-Xms -XX:InitialHeapSize 堆内存初始大小
-Xmx -XX:MaxHeapSize 堆内存最大大小
-Xss -XX:ThreadStackSize 单个线程栈大小
-XX:NewSize 新生代初始堆大小
-XX:MaxNewSize 新生代最大堆大小
-XX:OldSize 老年代堆大小
-XX:MetaspaceSize 元数据区初始值(JDK1.8)
-XX:MaxMetaspaceSize 元数据区最大值(JDK1.8)
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例。默认值为8-XX:InitialTenuringThreshold 对象从新生代晋升到老年代的初始年龄阈值,默认值15
-XX:MaxTenuringThreshold 对象从新生代晋升到老年代的最大年龄阈值,默认值15
-XX:NewRatio 老年代/新生代的堆内存比例,在设置了-XX:MaxNewSize的情况下,-XX:NewRatio的值会被忽略,Oracle JDK 8中,默认值通常为2-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintGC
-XX:+PrintGCDetails-XX:+PrintTenuringDistribution JVM 在每次新生代GC时,输出幸存区中对象的年龄分布
对象进入老年代相关JVM参数
-XX:PretenureSizeThreshold 大对象直接进入老年代
-XX:TargetSurvivorRatio 默认值为survivor区域的一半,年龄N和年龄N以上的对象占survivor区域的比例大于这个值时,直接进入老年代
-XX:MaxTenuringThreshold 对象从新生代晋升到老年代的最大年龄阈值,默认值15
使用jinfo查看进程的JVM参数
jinfo -flags <pid>
查看JVM参数默认值的方法
java -XX:+PrintFlagsFinal -version
参考文档
GC(Allocation Failure)引发的一些JVM知识点梳理
https://blog.csdn.net/zc19921215/article/details/83029952
[case9]频繁GC (Allocation Failure)及young gc时间过长分析
https://segmentfault.com/a/1190000013509330
JVM常用参数说明
https://www.cnblogs.com/shjr/p/14667216.html
CMS的CMSInitiatingOccupancyFraction解析
https://blog.csdn.net/insomsia/article/details/91802923
CMS收集器几个参数详解 -XX:CMSInitiatingOccupancyFraction, CMSFullGCsBeforeCompaction
https://blog.csdn.net/liubenlong007/article/details/88541589
Java开发中的问题排查,性能调优,先学会阅读GC日志
https://cloud.tencent.com/developer/article/1478419
JVM中的垃圾收集器 -- CMS
https://blog.csdn.net/wodewutai17quiet/article/details/48895893