7

Java 虚拟机原理 (四) G1垃圾收集器入门

 3 years ago
source link: https://blog.duval.top/2021/01/10/Java-%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%8E%9F%E7%90%86-4-G1%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8%E5%85%A5%E9%97%A8/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文翻译自《Getting Started with the G1 Garbage Collector》

本教程介绍了如何使用G1垃圾收集器以及如何在Hostspot虚拟机中使用它。你可以学习到G1垃圾收集器的内部运行机制、切换到使用G1垃圾回收器的命令行关键参数以及打印GC日志的选项参数。

预估阅读时间

大概1小时

本教程详细介绍了Java语言的G1垃圾收集器。主要内容分为五部分:

  • 通过介绍垃圾收集器以及性能来了解JVM虚拟机;
  • 逐步分析G1垃圾收集器的每一个过程;
  • 回顾CMS收集器的每一个过程;
  • 介绍G1垃圾收集器可用的一些选项参数;
  • 介绍G1垃圾收集器的日志参数。

软硬件配置要求

以下是软硬件要求列表:

  • PC(Window XP或更新、Mac OS X、Linux)
  • Java 7u9 或更新版本
  • 最新的Java 7样例以及压缩包

在开始本教程之前,你需要:

  • 下载安装最新的JDK(JDK 7u9或更新版本)JDK 7下载地址
  • 下载解压样例压缩包到相应目录,例如:C:\javademos

Java以及JVM技术

Java概览

Java是Sun Microsystems公司在1995年第一次发布的一门编程语言。它是构建包括游戏、商业、基础设施等Java应用程序的底层技术。在全世界范围内,Java运行在超过8.5亿个人电脑以及数十亿的手机电视设备上。总体上看,Java平台由以下几个关键的组件构成。

Java运行时版本

当你下载Java的时候,你可以获取Java运行时环境(JRE)。JRE是由Java虚拟机(JVM)、Java平台核心类和支撑Java平台的库构成。这三者都是Java应用运行所必需的组件。通过Java7,Java应用在个人电脑上的运行形式可以是操作系统桌面应用,也可以是通过Java Web Start从Web上安装的桌面应用,或者是内嵌在浏览器中的Web应用(使用JavaFX)

Java编程语言

Java是一门面向对象编程语言,包含以下特性:

  • 平台独立性 - Java应用会被编译成保存在class文件中的字节码,并且被加载进虚拟机中。因为Java应用总是运行在虚拟机中,所以Java应用可以运行在各种不同的操作系统和设备上;
  • 面向对象 - Java是一门面向对象的编程语言,具有很多与C、C++类似特性,并对其进行了改进;
  • 全自动垃圾收集 - Java会自动分配和回收内存,程序中无需关心这个过程;
  • 丰富的库资源 - Java有大量预置的类库可以进行诸如IO、网络传输、日期操作等任务。

Java开发工具包

Java开发工具包 The Java Development Kit (JDK) 是用来开发Java应用的一系列工具。通过JDK,你可以编译Java应用,并且运行在JVM中。此外JDK提供了打包和分发你的应用的工具。

JDK和JRE共用了Java应用编程接口(Java API)。Java API是用来创建Java应用的一系列预编译库。它使得开发一些常用的程序更加便捷,比如:字符串操作、日期时间处理、网络传输、实现数据结构(例如 lists, maps, stacks, and queues)。

Java虚拟机

Java虚拟机(JVM)是一个抽象的计算机器。JVM本质上是一个程序,它就像一台机器一样写入程序并且执行。因此,所有的Java程序都按照同一套接口和库写入到JVM中。每一个针对特定操作系统的JVM实现都将Java程序指令翻译为本地操作系统中的指令。这样,Java就实现了平台独立性。

Java虚拟机的第一个原型是由Sun Microsystems完成的,它在一个类似现在的个人数字代理(PDA)的手持设备上模拟了Java虚拟机指令集。现在Oracle已经实现在移动、桌面和服务器设备上模拟 Java 虚拟机,但Java虚拟机不承担任何特定的实现技术、主机硬件或操作系统。

(非关键内容,此处略)

探究JVM架构

Hotspot架构

HotSpot JVM 拥有一个支持强大特性和功能基础的架构,并支持实现高性能和大规模扩展性。例如HostSpot JVM JIT编译器生成动态优化。换句话说,是在运行 Java 应用程序时做出优化决策,并生成针对底层系统体系结构的高性能本地指令。此外,经过对它的运行时环境和多线程垃圾收集器的不断的成熟进化和持续的研发,HostSpot JVM在现今所有操作系统中都有很高的可拓展性。

HotSpot JVM架构图

HotSpot JVM架构图

JVM主要的组件包括类加载器、运行时数据区域以及执行引擎。(the class loader, the runtime data areas, and the execution engine)

HotSpot核心组件

JVM中与性能表现有关的核心组件已在下图高亮显示。
JVM核心组件

JVM核心组件

在调优JVM性能的时候,主要关注这三个组件。堆(heap)是你的对象数据存储的区域。该区域由程序启动指定的垃圾收集器管理。大多数调优选项都跟调整堆大小和选择最合适的垃圾回收器相关。JIT编译器对性能也有很大影响,但是在较新版本的JVM里很少需要调优。

通常在调优Java应用的时候,会关注两个主要的指标:响应时间和吞吐量。

响应时间就是应用或者系统应答所请求的数据的用时。例如:

  • 桌面UI响应时间用时;
  • 网站返回一个页面用时;
  • 数据库查询返回用时。

对所有的对响应时间敏感的应用来说,很长的暂停时间是不能接受的。此时应当关注实现更短的应答时间。

吞吐量指标关注的是该应用在一段时间内能处理的最大工作量。例如:

  • 一段时间内完成的事务数量;
  • 一个批处理程序一个小时内完成的任务数量;
  • 数据库在一个小时内完成的查询数量。

长暂停对这些关注吞吐量的应用来说是可以接受的。因为高吞吐量应用关注的一段时间内的基准测试,而快速响应并不是一个考量点。

G1垃圾收集器

G1垃圾收集器(Garbage-First collector)是一个面向多核心大内存机器的服务端收集器。
它能以大概率满足垃圾收集暂停时间目标,同时实现高吞吐量。Oracle JDK 7u4以及更新版本完全支持G1垃圾收集器。G1收集器专门为以下应用而设计:

  • 能够像CMS垃圾收集器一样和应用线程并发执行;
  • 无需较长的GC停顿时间来实现内存压缩;
  • 需要更可预测的GC暂停时间;
  • 不希望牺牲大量的吞吐量性能;
  • 不需要更大的Java堆空间。

G1计划作为CMS的长期替代。G1和CMS相比,两者的许多不同点使得G1成为更好的解决方案。其中一个不同就是G1是一个能内存压缩的收集器。G1的内存压缩使得能够完全避免使用细粒度的空闲列表来分配内存,而是依靠regions。这大大简化了垃圾收集器,并大大消除了潜在的碎片化问题。另外,G1比CMS提供了更可预测的垃圾收集暂停时间,允许用户指定期望的暂停时间。

G1 回收过程概览

以前的垃圾收集器(serial, parallel, CMS)都将堆划分为特定内存大小的3部分:年轻代、老年代和永久代。

Hotspot Heap Structure

Hotspot Heap Structure

所有的内存对象最终都会位于这三部分之一。
但G1垃圾收集器采用了不同的方法。

G1 Heap Allocation

G1 Heap Allocation

堆内存被划分成一系列相同大小的连续虚拟内存的区块(region)。这些区块都被分配了和以前收集器相同的角色(比如 eden, survivor, old),但相同角色的区块数量不是确定的。这使得堆内存使用上有非常大的灵活性。

当执行垃圾收集的时候,G1的工作方式和CMS类似。G1执行并发全局标记阶段来决定堆内的存活对象。当标记阶段完成后,G1知道哪些区块(regions)是最空闲的。它会优先收集这些区块,因为回收它们通常可以获得大量空闲内存。这也是为什么这种垃圾回收方式被称为Garbage-First。顾名思义,G1在它的收集和压缩阶段关注堆里最有可能包含大量可回收对象(也就是垃圾)的区域。G1使用一个暂停预测模型来满足用户指定的暂停时间目标,并根据指定的暂停时间目标来选择回收的区块数量。

被G1标记为可回收的区块会通过疏散方法(evacuation)来进行垃圾回收。G1从一个或者多个区块中复制对象到堆内的一个区块,在这个过程中会同时压缩和释放内存空间。为了减少停顿时间和提高吞吐量,这个疏散过程在多核心机器上是并发执行的。因此,在每一个垃圾收集过程中,G1在用户指定的停顿时间目标内不断地减少内存碎片。这超过了以往的两种回收方法的功能。CMS(Concurrent Mark Sweep)垃圾收集器没有进行内存压缩。ParallelOld垃圾回收器只进行全堆的压缩,会导致非常长的暂停时间。

但值得注意的是G1不是一个实时收集器。它会以较高概率但并不绝对保证满足用户设定的停顿时间。G1根据之前回收的数据来评估在用户指定的停顿时间内能够回收多少个区块(region)。

注意: G1既有并行阶段(与应用线程并行运行,例如:refinement, marking, cleanup等过程),也有并发阶段(多线程并发运行,例如stop the world)。Full GC依然是单线程的,但是如果调优到位的话,你的应用应该避免Full GC发生。

G1占用空间

如果你从ParallelOldGC或者CMS迁移到G1,你会发现JVM占用的进程空间更大。这主要是因为一些记录数据(”accounting” data structures),例如 RSets和CSets(Remembered Sets and Collection Sets)。

RSets跟踪了那些指向这个区块的对象。在堆中,每一个Region都对应着一个RSet。RSets使得区块能够并行且独立地进行回收。全部RSets占用的空间不超过5%。

CSets记录了在一次GC中将会被回收的区块集合。CSets里的所有存活的数据在GC中都会被压缩(复制整理)。这些区块角色可以是Eden, survivor, 或者 old generation。全部CSets占用的空间不会超过1%。

G1使用案例建议

G1首要关注的是在一个大堆中以有限的GC延迟运行用户应用。这意味着堆的大小应该在6GB或者更大,而且稳定运行的期望暂停时间应该在0.5s以内。

现在已经运行在CMS 或者 ParallelOldGC 垃圾收集器上的应用,如果考虑切换到G1,应当考虑是否满足以下一个或者多个条件:

  • Full GC持续时间太长太频繁;
  • 对象的分配速度和对象的晋升速度差异非常大;
  • 不希望长时间的GC收集以及压缩停顿时间。

注意: 如果你正在使用CMS 或者 ParallelOldGC,并且你的应用没有较长的GC停顿时间,那你应该继续使用当前的GC收集器。最新的JDK并不要求一定使用G1垃圾收集器。

回顾CMS垃圾收集器

接下来,让我们回顾CMS垃圾收集器的各个阶段。

堆内存结构

堆被划分为三个空间。

年轻代被划分为Eden区和两个Suvivor区。老年代是一个连续的内存空间。除非Full GC发生,否则不会发生内存压缩。

如何进行Young GC

下图中,新生代以绿色表示,而老年代用蓝色表示。当你的应用运行了一段时间后,CMS的运行情况大概如图所示。对象分散在老年代的各个角落。

How young GC works

How young GC works

在CMS回收过程中,老年代对象会原地回收。它们不会被移动到其他地方。堆空间在Full GC发生前不会被压缩。

年轻代收集过程

存活对象会从Eden区和其中一个Suvivor区复制到另外一个Suvivor区域。所有的超过老化阈值(aging threshold)的较老对象都会被晋升到老年代中。

Young Generration Collection

Young Generration Collection

年轻代收集之后

年轻代收集结束后,Eden区和其中一个Survivor区会被清空。
After Young GC

After Young GC

刚被晋升的对象在图里以深蓝色标注。绿色标注的对象是那些在新生代收集中存活且没有被晋升到老年代的对象。

CMS的老年代收集过程

这过程会有两个步骤发生stop the world,分别是:初始标记和重新标记。当老年代到达指定的占有率,CMS会被触发。

Old gen collection in CMS

Old gen collection in CMS

  • 初始标记是一个很短的阶段,用来标记那些存活的(可达的)对象;
  • 并发标记会在应用运行过程中寻找存活对象;
  • 最后那些在并发标记中被漏掉的对象会在重新标记阶段被标记。

老年代收集过程 — 并发清除

那些在之前的阶段中没有被标记的对象会被清除掉。但在这个过程中并没有内存压缩。
Old Gen Collection - concurrent sweep

Old Gen Collection - concurrent sweep

注意: 未标记对象 == 死亡对象

老年代收集过程 — 并发清除之后

在并发清除结束之后,大量的内存空间会被释放出来。此外,你还会注意到并没有发生内存压缩。
Old Gen Collection - After Sweep.png

Old Gen Collection - After Sweep.png

最后CMS垃圾收集器会进入重置阶段,然后等待下一次达到GC触发阈值。

G1垃圾收集器步骤分析

G1垃圾收集器步骤分析

G1垃圾收集器使用了一种不同的方式去分配堆空间。下面各图会逐步介绍G1的各个步骤。

1.G1堆空间(G1 Heap Structure)

堆空间会被分割成许多相同大小的区块(regions)。
G1 Heap Structure.png

G1 Heap Structure.png

区块大小在JVM启动之初就会决定。JVM通常会分配大小为1Mb到32Mb大小范围内的大约2000个区块。

2.G1堆空间分配(G1 Heap Allocation)

实际上,这些区块会被分配为Eden、Survivor和Old Generation等逻辑区域。
G1 Heap Allocation.png

G1 Heap Allocation.png

上图中不同颜色的区块表示的是不同的角色。存活的对象会从一个区块撤离到另一个区块。区块被设计为并发地进行收集,而不停止其他的应用线程。

就像上图展示的,所有的区块都被分配为Eden、Suvivor和Old等角色。此外,还有第四种类型 – 大区块(Humongous regions)。这些大区块被设计为存放那些占用一半或者更大的区块大小的对象。他们被存放在若干个连续的区块中。最后一种类型的区块就是堆中还没使用的部分。

注意: 在写这篇文章的时候,大对象的回收还没被优化。因此,你应该尽量避免创建大对象。

3.G1的年轻代(Young Generation in G1)

堆空间被划分为大概2000个区块。区块的大小在1Mb~32Mb之间。蓝色的区块存放的是老年代对象,而绿色的区块存放着年轻代对象。
Young Generation in G1.png

Young Generation in G1.png

值得注意的是,不像以前的垃圾收集器那样,G1里这些区块并不要求是连续的内存空间。

4.G1的一次 Young GC(A Young GC in G1)

存活对象会被疏散(复制或者移动)到一个或者多个suvivor区块。如果对象超过了老化阈值,则会被晋升到老年代区块中。

A Young GC in G1.png

A Young GC in G1.png

这个是一个stop the world (STW)的过程。这时候会为下一次Young GC计算Eden区和Suvivor区的大小。

这种方式可以非常容易地按照需要调整区块大小。

5.G1的Young GC结束之后(End of a Young GC with G1)

存活对象都被疏散到Suvivor区块或者Old区块。

End of Young GC with G1.png

End of Young GC with G1.png

上图中,最新被晋升老年代的对象以深蓝色标注,而Suvivor区块以深绿色标注。

总结上述,G1的年轻代回收可以归纳为以下:

  • 堆空间就是一个被分割为多个区块的内存空间。
  • 年轻代内存空间由一系列不连续的区块组成。这使得按需调整大小非常容易。
  • 年轻代垃圾回收过程是一个stop the world过程。所有的应用线程都会被停止。
  • 年轻代回收过程是一个多线程并发过程。
  • 存活的对象会被复制到新的Suvivor区块以及Old区块。

G1的老年代收集过程

跟CMS收集器类似,G1收集器被设计为用于老年代对象的低延迟收集器。以下表格介绍了G1在老年代上的收集过程。

G1垃圾收集的阶段 - 并发标记周期阶段

G1收集器在老年代上执行以下的几个步骤。值得注意的是,下面有些步骤其实是年轻代收集过程的一部分。

阶段 过程描述 (1)初始标记
(Stop the World Event) stop the world。这个过程发生在 young gc,用于标记那些引用了老年代对象的 Suvivor 区块(根区块 root regions) (2)扫描根区块 扫描那些引用了老年代的 Suvivor 区块(根区块)。这个阶段是和应用线程并行执行。这个阶段必须在下一次 young gc 发生之前完成。 (3)并发标记 在全堆范围内寻找存活对象。这个阶段也是和应用线程并行执行。这个阶段可以被年轻代垃圾收集过程所打断 (4)重新标记
(Stop the World Event) 完成堆中存活对象的标记。这个阶段使用了一个叫 SATB(snapshot-at-the-beginning) 的算法,该算法比 CMS 收集器所用算法快非常多。 (5)并发清理
(Stop the World Event and Concurrent) 1)核算存活对象和完全空闲区块的记录数据(stop the world); 2)擦除 RSets(stop the world); 3)重置空区块,并且添加进空闲区块列表(并发)。 (*)复制
(Stop the World Event) 这是会发生多次的 STW 过程,过程中会疏散或者复制存活对象那到新的未使用区块上。复制过程可以发生在年轻代区块中,GC日志中显示为 [GC pause (young)],或者同时发生在年轻代和老年代区块中,而GC日志中显示为 [GC Pause (mixed)]

逐步分析G1的老年代收集过程

经过以上的各阶段的简单介绍后,我们来看看他们在G1收集器的老年代回收过程中是如何相互影响的。

6.初始标记阶段(Initial Marking Phase)

针对存活对象的初始标记在年轻代垃圾收集阶段已经完成。在GC日志中显示为GC pause (young)(inital-mark)。

Initial Marking Phase.png

Initial Marking Phase.png

7.并发标记阶段(Concurrent Marking Phase)

如果发现空区块(下图用”X”标记),他们会在重新标记阶段直接移除。此外,决定存活情况的记录信息也会被计算。

Concurrent Marking Phase.png

Concurrent Marking Phase.png

8.重新标记阶段(Remark Phase)

空区块会被删除并且回收。所有区块现在都会计算存活情况。
Remark Phase.png

Remark Phase.png

9.复制清理阶段(Copying/Cleanup Phase)

G1会选择存活对象最少的区块来进行回收,因为这些区块回收速度会最快。这些区块会在一次young GC中被同时回收。GC日志中这个过程显示为[GC pause (mixed)]。因此,年轻代和老年代会在同一时间进行回收。

Copying:Cleanup Phase.png

Copying:Cleanup Phase.png

10.复制清理阶段之后(After Copying/Cleanup Phase)

那些被选中的区块已经被收集并且压缩到了下图的深蓝色和深绿色的区块中。

After Copying:Cleanup Phase.png

After Copying:Cleanup Phase.png

老年代回收过程总结

我们可以对G1的老年代收集过程总结几个要点。

  • 并发标记阶段
    • 存活信息会在应用运行的时候并行计算;
    • 这些存活信息指出了在疏散暂停时候哪些区块是最应该回收的;
    • 没有类似CMS那样的清除阶段(sweeping phase)。
  • 重新标记阶段
    • 使用比CMS所用算法更快的SATB(Snapshot-at-the-Beginning)算法;
    • 完全为空的区块会被回收;
  • 复制/清理阶段
    • 年轻代和老年代会被同时回收;
    • 基于对象存活情况,挑选老年代区块。

命令行参数可选项以及最佳时间

这个章节里,我们会学习G1的各种命令行选项。

命令行基本参数

为了使用G1收集器,需要指定参数:-XX:+UseG1GC

这是启动Java进程的一个样例:

java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar

命令行关键参数

-XX:+UseG1GC - 告诉JVM使用G1垃圾收集器;

-XX:MaxGCPauseMillis=200 - 设置一个最大的GC停顿时间目标。这是一个软目标,JVM会尽最大能力去满足它。因此,停顿时间有时候并不会满足这个目标。默认值是200ms。

-XX:InitiatingHeapOccupancyPercent=45 - 触发并发GC的堆内存占用阈值。G1收集器根据整个堆的占用情况而不是某个分代来触发一次并发回收循环。如果这个值指定为0,则意味着会进行恒定的GC循环。默认值为45(占用45%)。

不要设置年轻代的大小

通过 -Xmn 显式设置年轻代大小,会干扰到G1的默认行为。

  • G1不会再遵循回收停顿目标。因此实际上,设置年轻代大小会禁用停顿目标;
  • G1再也不能够按需要扩增或者收缩年轻代大小。因为大小已经被固定下来而不能做任何更改。

响应时间指标

与其使用平均响应时间(ART)作为一个指标去设置XX:MaxGCPauseMillis=N,不如考虑设置一个值,使得90%或更多时间满足这个停顿目标。这意味着90%的用户发起请求后得到应答的时间不会超过这个目标。请记住,这个停顿时间只是一个目标,而不能保证总是能满足它。

什么是疏散失败?(Evacuation Failure)

当JVM对幸存对象进行回收或者在晋升对象到老年代的时候发生堆内存耗尽,将会触发一次晋升失败。此时堆空间不能再扩增,因为它已经处于最大值。当指定参数XX:+PrintGCDetails的时候,这种晋升失败的情况在日志里体现为:to-space overflow。而且这个过程会付出昂贵代价!

  • GC必须继续下去,所以必须得释放出空间;

  • 未能成功复制的对象必须安置到适当位置;

  • 对CSet中的RSets的区块的任何更新都需要重建;(这句太难翻译了,原文是:Any updates to RSets of regions in the CSet have to be regenerated.)

  • 所有这些步骤都是代价昂贵。

    如何避免疏散失败

    为了避免疏散失败,考虑以下选项。

  • 扩大堆空间

    • 增大-XX:G1ReservePercent=n,默认值是10;
    • G1会设定一个虚拟上限,通过让这些保留内存空间闲置着,以防发生更多的”to-space”而需要这些内存。
  • 更早启动标记阶段

  • 通过-XX:ConcGCThreads=n参数,来增大标记线程数量

G1完整的GC开关

以下是G1 GC开关列表。请牢记上文提到的最佳实践。

GC选项 描述 -XX:+UseG1GC 指定使用G1垃圾收集器 -XX:MaxGCPauseMillis=n 设定一个最大的GC停顿时间目标。这是一个软目标,JVM会尽最大努力满足这个目标 -XX:InitiatingHeapOccupancyPercent=n 触发并发GC的堆内存占用阈值。G1收集器根据整个堆的占用情况而不是某个分代来触发一次并发回收循环。如果这个值指定为0,则意味着会进行恒定的GC循环。默认值为45(占用45%)。 -XX:NewRatio=n 新生代/老年代的大小比率,默认值是2 -XX:SurvivorRatio=n Eden区/Suvivor区的比率,默认值是8 -XX:MaxTenuringThreshold=n 最大的存活次数。默认值是15 -XX:ParallelGCThreads=n 设置并行阶段GC线程数量。不同平台上的默认值各不一样。 -XX:ConcGCThreads=n 并发阶段的GC线程数量。不同平台上的默认值各不一样. -XX:G1ReservePercent=n 设置堆的保留空间阈值以降低发生晋升失败的可能。默认值是10 -XX:G1HeapRegionSize=n G1将堆空间划分为相同大小的区块。这个参数设置初始化时候的区块数量。该参数的默认值是根据堆空间来动态确定的。最小值为1Mb,最大值为32Mb

G1的GC日志

最后我们来看如何通过日志信息来分析G1收集器的表现。这个章节会介绍可以用来在日志中收集数据以及信息的参数。

设置日志详情

你可以设置三个不同级别的日志详情。

(1)-verbosegc(与 -XX:+PrintGC 相同)设置详情日志的级别为 fine

样例输出

[GC pause (G1 Humongous Allocation) (young) (initial-mark) 24M- >21M(64M), 0.2349730 secs]
[GC pause (G1 Evacuation Pause) (mixed) 66M->21M(236M), 0.1625268 secs] 

(2) -XX:+PrintGCDetails 设置详情日志级别为 finer。这个选项展示以下信息:

  • 各个阶段的平均用时、最小用时和最大用时;
  • 根扫描、RSet更新、RSet扫描、对象复制、终止(和尝试次数);
  • 另外也展示了其他时间,比如选择CSet时间、引用处理时间、引用入队时间和冻结CSet耗时;
  • 展示Eden、Suvivor以及全堆的占用情况。

样例输出

[Ext Root Scanning (ms): Avg: 1.7 Min: 0.0 Max: 3.7 Diff: 3.7]
[Eden: 818M(818M)->0B(714M) Survivors: 0B->104M Heap: 836M(4096M)->409M(4096M)]

(3) -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest 设置详情级别为 finest*。跟finer级别类似,但包含了个别工作线程的信息。

[Ext Root Scanning (ms): 2.1 2.4 2.0 0.0
           Avg: 1.6 Min: 0.0 Max: 2.4 Diff: 2.3]
       [Update RS (ms):  0.4  0.2  0.4  0.0
           Avg: 0.2 Min: 0.0 Max: 0.4 Diff: 0.4]
           [Processed Buffers : 5 1 10 0
           Sum: 16, Avg: 4, Min: 0, Max: 10, Diff: 10]

指定时间参数

有一些开关决定打印GC日志中的时间。

(1)-XX:+PrintGCTimeStamps - 当JVM启动后,打印运行时间。

样例输出

1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]

(2)-XX:+PrintGCDateStamps - 每条记录都打印日期前缀。

样例输出

2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]

认识G1日志

为了认识GC日志,这个章节使用实际的GC日志输出定义一些术语。以下的例子展示了日志的输出,并且附带了术语的解释。

注意:* 了解更多信息请移步Poonam Bajaj’s Blog post on G1 GC logs.

G1日志术语

  • 并行时间(Parallel Time)

      414.557: [GC pause (young), 0.03039600 secs] [Parallel Time: 22.9 ms]
      [GC Worker Start (ms): 7096.0 7096.0 7096.1 7096.1 706.1 7096.1 7096.1 7096.1 7096.2 7096.2 7096.2 7096.2
          Avg: 7096.1, Min: 7096.0, Max: 7096.2, Diff: 0.2]

    Parallel Time - 暂停的主要并行部分的大体运行时间;
    Worker Start - workers开始的时间戳;
    注意: 这些日志都按照线程ID排序,并且每个条目都是一致的。

  • 外部根扫描(External Root Scanning)

      [Ext Root Scanning (ms): 3.1 3.4 3.4 3.0 4.2 2.0 3.6 3.2 3.4 7.7 3.7 4.4
          Avg: 3.8, Min: 2.0, Max: 7.7, Diff: 5.7]

    External root scanning - 扫描外部根(例如指向堆空间的系统字典)所耗时间

  • 更新RSet(Update Remembered Set)

      [Update RS (ms): 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Avg: 0.0, Min: 0.0, Max: 0.1, Diff: 0.1]
      [Processed Buffers : 26 0 0 0 0 0 0 0 0 0 0 0
          Sum: 26, Avg: 2, Min: 0, Max: 26, Diff: 26]

    Update Remembered Set - Any buffers that are completed but have not yet been processed by the concurrent refinement thread before the start of the pause have to be updated. Time depends on density of the cards. The more cards, the longer it will take.

  • 扫描RSet(Scanning Remembered Sets)

      [Scan RS (ms): 0.4 0.2 0.1 0.3 0.0 0.0 0.1 0.2 0.0 0.1 0.0 0.0 Avg: 0.1, Min: 0.0, Max: 0.4, Diff: 0.3]F

    Scanning Remembered Sets - Look for pointers that point into the Collection Set.

  • 对象复制(Object Copy)

      [Object Copy (ms): 16.7 16.7 16.7 16.9 16.0 18.1 16.5 16.8 16.7 12.3 16.4 15.7 Avg: 16.3, Min: 12.3, Max:  18.1, Diff: 5.8]

    Object copy – The time that each individual thread spent copying and evacuating objects.

  • Termination Time

      [Termination (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
      0.0 Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0] [Termination Attempts : 1 1 1 1 1 1 1 1 1 1 1 1 Sum: 12, Avg: 1, Min: 1, Max: 1, Diff: 0]

    Termination time - When a worker thread is finished with its particular set of objects to copy and scan, it enters the termination protocol. It looks for work to steal and once it’s done with that work it again enters the termination protocol. Termination attempt counts all the attempts to steal work.

  • GC Worker End

      [GC Worker End (ms): 7116.4 7116.3 7116.4 7116.3 7116.4 7116.3 7116.4 7116.4 7116.4 7116.4 7116.3 7116.3
          Avg: 7116.4, Min: 7116.3, Max: 7116.4, Diff:   0.1]
      [GC Worker (ms): 20.4 20.3 20.3 20.2 20.3 20.2 20.2 20.2 20.3 20.2 20.1 20.1
          Avg: 20.2, Min: 20.1, Max: 20.4, Diff: 0.3]

    GC worker end time – Timestamp when the individual GC worker stops.

    GC worker time – Time taken by individual GC worker thread.

  • GC Worker Other

      [GC Worker Other (ms): 2.6 2.6 2.7 2.7 2.7 2.7 2.7 2.8 2.8 2.8 2.8 2.8
          Avg: 2.7, Min: 2.6, Max: 2.8, Diff: 0.2]

    GC worker other – The time (for each GC thread) that can’t be attributed to the worker phases listed previously. Should be quite low. In the past, we have seen excessively high values and they have been attributed to bottlenecks in other parts of the JVM (e.g., increases in the Code Cache occupancy with Tiered).

  • Clear CT

      [Clear CT: 0.6 ms]

    Time taken to clear the card table of RSet scanning meta-data

  • Other

      [Other: 6.8 ms]

    Time taken for various other sequential phases of the GC pause.

  •   [Choose CSet: 0.1 ms]

    Time taken finalizing the set of regions to collect. Usually very small; slightly longer when having to select old.

  • Ref Proc

      [Ref Proc: 4.4 ms]

    Time spent processing soft, weak, etc. references deferred from the prior phases of the GC.

  • Ref Enq

      [Ref Enq: 0.1 ms]

    Time spent placing soft, weak, etc. references on to the pending list.

  • Free CSet

      [Free CSet: 2.0 ms]

    Time spent freeing the set of regions that have just been collected, including their remembered sets.

在本文中,你已经浏览学习了Java JVM里的G1垃圾收集器。首先你学习为何堆和垃圾收集器是Java JVM的重要组成部分。然后回顾了CMS和G1垃圾收集器是如何工作的。接着学习了G1的命令行开关以及使用它们的最佳实践。最后学习了GC日志中的打印对象以及数据。

在这个教程中,你学习了:

  • Java JVM的组成部分;
  • G1收集器的概述;
  • 回顾CMS收集器;
  • 回顾G1收集器;
  • 命令行开关和最佳实践;
  • G1的日志。

更多相关信息请查看以下网页链接:

  • Curriculum Developer: Michael J Williams
  • QA: Krishnanjani Chitta

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK