GC 玩起来(二)垃圾回收器特征详解

持续创作,加速成长!这是我参与「掘金日新计划 · 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
复制代码

图中显示 UseParallelGCUseParallelOldGC 为 'true' ,与理论上的 Parallel Scavenge + Parallel Old 一致。

单逻辑 CPU 环境采坑: 使用 -XX:+PrintFlagsFinal 所打印出的 6 种垃圾回收器的开启状态均为 false

3.3 使用 jinfo -flag

使用 jinfo -flag 来逐一确认应用是否启用了指定的垃圾回收器:

jinfo -flag UseXXXGC 应用端口号
复制代码

图中显示 UseParallelGCUseParallelOldGC 为 '+' 号开启,其余 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 日志的生成与分析方法。

猜你喜欢

转载自juejin.im/post/7103912966776422436
今日推荐