持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
摘要
GC 日志是判断 Java 应用程序内存是否存在故障的重要判断依据。《GC 玩起来》这个小系列,期望能够使零基础的读者快速理解 GC 相关的重要概念,最终掌握 GC 日志的分析方法。
第二篇《垃圾回收器特征详解》旨在梳理 JDK1.8 下的各类垃圾回收器,了解不同垃圾回收器对于老年代、新生代这些概念的不同别称。
1 垃圾回收器的分类
JVM 的垃圾回收器是垃圾清理、内存回收算法的具体实现。JVM 包含的垃圾回收器见下图(基于 JDK1.8 ):
图中的连线部分,代表着垃圾回收器可以相互配合使用。
对于图中的 7 类垃圾回收器,我们可以做如下的简单理解:
-
Serial:串行回收器,是一个单线程的回收器。
-
Parallel:并行回收器,在多核 CPU 下表现良好; ParNew 是 Serial 的多线程版本; Parallel Scavenge 与 ParNew 类似,更加追求吞吐量的可控。
吞吐量
吞吐量指 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值。
吞吐量 = 用户代码运行时间 / ( 用户代码运行时间 + 垃圾回收时间 )
-
CMS(Concurrent Mark Sweep):基于并发标记清除算法,应用程序线程与 GC 线程交替执行,尽可能降低应用停顿时间,但会影响应用的整体吞吐量和性能。
-
G1:G1 回收器可以进行 Mixed GC ,对于新生代的收集类似于 ParNew ,对于老年代的收集则类似于 CMS ,但相对于 CMS , G1 不会产生内存空间碎片,还可以指定明确的最大停顿时间。
2 垃圾回收器相关的 JVM 参数
我们可以通过 JVM 参数来指定应用使用哪种垃圾回收器组合:
JVM 参数 | 新生代垃圾回收器 | 老年代垃圾回收器 |
---|---|---|
-XX:+UseSerialGC | Serial | Serial Old |
-XX:+UseParallelGC (同 -XX:+UseParallelOldGC ) |
Parallel Scavenge | Parallel Old |
-XX:+UseParNewGC | ParNew | Serial Old |
-XX:+UseConcMarkSweepGC | ParNew | CMS + Serial Old |
-XX:+UseG1GC | G1 | G1 |
3 如何查看默认的垃圾回收器
通常情况下,我们没有特意去指定应用使用哪种垃圾回收器(实际上好像也确实没有这个必要)。
查阅网上的资料, JDK1.8 默认使用 Parallel Scavenge + Parallel Old 。出于好奇,很想实际确认一下这个结论,结果因为服务器环境的问题(一开始选择的是一台单逻辑 CPU 的虚机,各种方法各种不正常,后来换了一台 8 逻辑 CPU 的虚机就与理论一致了),绕了很多弯,采了很多坑。现将所实践的 6 种方法记录如下(突然想起了《孔乙己》中茴香豆的“茴”字有四种写法):
查看逻辑 CPU 数量:
cat /proc/cpuinfo| grep "processor"| wc -l 复制代码
3.1 使用 -XX:+PrintCommandLineFlags
使用 -XX:+PrintCommandLineFlags
打印命令行参数,可以在参数中看到默认使用的垃圾回收器:
java -XX:+PrintCommandLineFlags -version
复制代码
图中显示的 -XX:+UseParallelGC
与理论上的 Parallel Scavenge + Parallel Old 一致。
单逻辑 CPU 环境采坑: 使用 -XX:+PrintCommandLineFlags
所打印出的命令行参数,没有使用垃圾回收器的相关信息。
3.2 使用 -XX:+PrintFlagsFinal
使用 -XX:+PrintFlagsFinal
查看 GC 垃圾回收器的开启状态:
java -XX:+PrintFlagsFinal -version | grep Use | grep GC
复制代码
图中显示 UseParallelGC
与 UseParallelOldGC
为 'true' ,与理论上的 Parallel Scavenge + Parallel Old 一致。
单逻辑 CPU 环境采坑: 使用 -XX:+PrintFlagsFinal
所打印出的 6 种垃圾回收器的开启状态均为 false
。
3.3 使用 jinfo -flag
使用 jinfo -flag
来逐一确认应用是否启用了指定的垃圾回收器:
jinfo -flag UseXXXGC 应用端口号
复制代码
图中显示 UseParallelGC
与 UseParallelOldGC
为 '+' 号开启,其余 4 种垃圾回收器的 JVM 参数为 '-' 号禁用,与理论上的 Parallel Scavenge + Parallel Old 一致。
单逻辑 CPU 环境采坑: 使用 jinfo -flag
得到的所有的 UseXXXGC
参数均为 '-' 号禁用状态。
3.4 使用 jmap -heap
分析 jmap -heap
命令的输出结果,通过关键字可以明确具体的垃圾收集器类型:
jmap -heap 应用端口号
复制代码
首先总结各类垃圾回收器对应的 jmap -heap
信息特征:
JVM 参数 | 对于垃圾回收器的描述 | 新生代的别称 | 老年代的别称 |
---|---|---|---|
-XX:+UseSerialGC | Mark Sweep Compact GC | New Generation | tenured generation |
-XX:+UseParallelGC | Parallel GC with X thread(s) | PS Young Generation | PS Old Generation |
-XX:+UseParallelOldGC | Parallel GC with X thread(s) | PS Young Generation | PS Old Generation |
-XX:+UseParNewGC | using parallel threads in the new generation. Mark Sweep Compact GC |
New Generation | tenured generation |
-XX:+UseConcMarkSweepGC | Concurrent Mark-Sweep GC | New Generation | concurrent mark-sweep generation |
-XX:+UseG1GC | Garbage-First (G1) GC with 1 thread(s) | G1 Young Generation | G1 Old Generation |
然后我们对比一下未明确指定垃圾回收器的应用的信息,特征符合 UseParallelGC
(同 UseParallelOldGC
),与理论上的 Parallel Scavenge + Parallel Old 一致:
单逻辑 CPU 环境采坑: 使用 jmap -heap
得到的信息特征符合 UseSerialGC
,与理论上的 Parallel Scavenge + Parallel Old 不一致。
3.5 使用 GarbageCollectorMXBean
通过 java.lang.management.GarbageCollectorMXBean
类可以获取应用的垃圾回收器相关信息。
首先编写一个 Java 文件,内容如下:
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
public class PrintGCInfo {
public static void main(String[] args) throws InstanceNotFoundException, MalformedObjectNameException,
ReflectionException, MBeanException, NullPointerException {
Object flags = ManagementFactory.getPlatformMBeanServer().invoke(
ObjectName.getInstance("com.sun.management:type=DiagnosticCommand"), "vmFlags", new Object[] { null },
new String[] { "[Ljava.lang.String;" });
for (String f : ((String) flags).split("\\s+"))
if (f.contains("GC"))
System.out.println(f);
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans())
System.out.printf("%-20s%s%n", gc.getName(), Arrays.toString(gc.getMemoryPoolNames()));
}
}
复制代码
通过 javac
编译后,再执行,即可得到垃圾回收器相关信息:
图中显示的 -XX:+UseParallelGC
与理论上的 Parallel Scavenge + Parallel Old 一致。
明确指定各类垃圾回收器,再运行此类,我们就可以得到各类特征信息:
单逻辑 CPU 环境采坑: 通过 java.lang.management.GarbageCollectorMXBean
得到的信息特征符合 UseSerialGC
,与理论上的 Parallel Scavenge + Parallel Old 不一致。
3.6 使用 GC 日志
使用 -XX:+PrintGCDetails
可以生成详细的 GC 日志。
java -Xms512m -Xmx2g -XX:+PrintGCDetails -Xloggc:gc.log -jar xxx.jar
复制代码
日志的开头部分,会打印出 JVM 运行参数,其中包括所使用的垃圾回收器类型,图中所显示的 -XX:+UseParallelGC
与理论上的 Parallel Scavenge + Parallel Old 一致。
配合 -XX:+UseXXXGC
选项,我们就可以得到不同垃圾回收器产生的 GC 日志,这些日志中对于老年代、新生代的描述是不同的,总结的特征信息如下:
JVM 参数 | 日志中对新生代的别称 | 日志中对老年代的别称 |
---|---|---|
-XX:+UseSerialGC | DefNew | Tenured |
-XX:+UseParallelGC | PSYoungGen | ParOldGen |
-XX:+UseParallelOldGC | PSYoungGen | ParOldGen |
-XX:+UseParNewGC | ParNew | Tenured |
-XX:+UseConcMarkSweepGC | ParNew | CMS |
-XX:+UseG1GC | 没有专门的新生代描绘,有 Eden 和 Survivors | 没有专门的老年代描绘,有 Heap |
单逻辑 CPU 环境采坑: 日志中找不到类似 -XX:+UseXXXGC
这样的使用垃圾回收器的相关信息。
以上就是不同垃圾回收器的特征介绍,下一篇文章将着重介绍 GC 日志的生成与分析方法。