G1垃圾回收器起步

感兴趣的朋友可以关注"猿学堂社区",系统化技术内容分享平台。或加入“猿学堂社区”微信交流群。
猿学堂师资均为一线互联网资深技术专家,提供专业、深度、体系化的技术课程,一站式技术解决方案。与技术专家线上线下深度互动、答疑解惑。在线学习笔记与课程完美结合。

原文地址

概述

目的

本教程涵盖了如何使用G1垃圾回收器以及G1如何与Hotspot JVM一起使用的基础知识。你将了解G1回收器内部的功能、用于启用G1的关键命令行开关以及输出G1操作日志的选项。

完成所需时间

大约1小时

介绍

本教程涵盖了JVM G1垃圾回收器的基础知识。第一部分提供了JVM概述以及垃圾回收和性能介绍。接下来回顾了Hotspot JVM中CMS垃圾回收器是如何工作的。然后,一步步指导在Hotspot JVM中使用G1回收器时,垃圾回收是如何工作的。在这之后,提供了一个小节涵盖了G1垃圾回收器与垃圾回收相关的命令行选项。最后,你将了解使用G1回收器时的日志选项。

软硬件需求

下面是一个软硬件需求列表:

  • 一个运行Windows XP或更高版本、Mac OS X或者Linux的PC。注意,操作这些操作在Windows7上完成,未在所有平台进行测试。尽管如此,在OS X或Linux应该工作正常。此外,可选择多核的机器。
  • Java 7 Update 9或更高版本
  • 最新的Java 7 Demo和Samples压缩文件

前提条件

开始该教程之前,你应该:

  • 如果未安装JDK,请下载并安装最新版本JDK(JDK 7 U9或更高)。
  • 下载并安装Demos和Samples压缩文件(与JDK下载在同一页面)。解压文件到个目录。例如:C:\javademos。

Java技术及JVM

Java概述

Java是一种编程语言和计算平台,由Sun公司首次发布于1995年。它是驱动Java程序包括工具、游戏、业务应用程序的基础技术。Java运行在全世界超过8亿5000万台个人电脑上以及全世界数十亿美元的设备,包括手机和TV设备。Java由一系列关键组件组成,作为一个整体创建Java平台。

JRE

当你下载Java,你将获得Java运行时环境(JRE)。JRE由JVM、Java平台核心类以及Java平台支持库组成。这三部分对于在你的电脑上运行Java应用是必须的。对于Java7,Java应用可以作为来自操作系统的桌面应用(译者注:Web应用+Servlet容器应属于该类)、使用JavaWeb Start从Web安装的桌面应用或者浏览器中的一个Web嵌入式应用运行(使用JavaFX)。

Java编程语言

Java是一种面向对象的编程语言,包含以下特征:

  • 平台独立——Java应用程序编译为字节码,存储在类文件中并由JVM加载。由于应用程序运行在JVM中,所以它们可以运行于许多不同的操作系统和设备上。
  • 面向对象——Java是面向对象的语言,采取了C和C++的许多特征并改善了它们。
  • 自动垃圾回收——Java自动分配和回收内存,因此应用程序不必承担该任务。
  • 丰富的标准库——Java包含了大量的预构建的对象诸如I/O、网络和日期操作等任务。

JDK

Java开发工具包(JDK)是一个Java应用程序开发工具集合。通过使用JDK,你可以编译Java编写的应用程序并在JVM中运行它们。除此之外,JDK提供了打包和发布应用的工具。

JDK和JRE共享Java应用程序接口(Java API)。Java API是开发人员用来创建Java应用的预先打包的库的集合。Java API通过提供工具来完成大部分常见的编程任务如字符串操作、日期/时间处理、网络操作和数据结构实现(如List、Map、Stack、Queue),使得开发更加简单。

JVM

JVM是一个抽象的计算机器。它是一个程序,像计算机一样,编写的程序可以在它里面运行。通过这种方式,Java程序可以编写为相同的接口和库的集合。对于针对具体操作系统的JVM实现,转换Java程序指令为本地操作系统的指令和命令。通过这种方式,Java程序实现了平台独立。

JVM的第一个原型实现由Sun公司完成,在类似于现在PDA这种手持设备的软件中模拟JVM指令集。Oracle现在已实现在手机、桌面和服务器设备上模拟JVM,但是JVM不承担任何特定的实现技术、主机的硬件或主机操作系统。它不是天生解释性的,但是也可以通过编译它的指令集到硅CPU上来实现。它还可以在微代码或直接在硅上实现。

Java虚拟机不知道Java编程语言的任何信息,除了一种特殊的二进制格式,即类文件格式。一个类文件包含了JVM指令集(或字节码)以及一个符号表和其它辅助信息。

为了安全起见,JVM对类文件中的代码施加了强语法和结构约束。不管如何,任何能够以一个有效的类文件进行描述的功能性语言都可以由JVM托管。吸引人的广泛应用、机器独立的平台,其它语言的实现者可以把JVM当做他们语言的载体。

JVM架构探索

HotSpot架构

HotSpot JVM的架构支持一个强大的功能和能力基础,支持实现高性能和大规模可伸缩性的能力。例如,HotSpot JVM JIT编译器生成动态优化。换句话说,它们在Java应用程序运行时制定优化策略并生成针对下层系统架构的高性能本地机器指令。此外,通过JVM运行环境的成熟演变和持续工程,HotSpot JVM具有高度可伸缩性,即使在最大的可用计算机系统中。

G1垃圾回收器起步_第1张图片

JVM的主要组件包括类加载器、运行时数据区域以及执行引擎。

关键Hotspot组件

与性能相关的JVM关键组件在下图中高亮显示:

G1垃圾回收器起步_第2张图片

调整性能时,JVM有三个组件需要关注。heap(堆)是对象数据存储的地方。该区域由启动时选择的垃圾回收器管理。大多数调整选项与堆大小以及为你的情况选择最合适的垃圾回收器有关。JIT编译器对于性能也有很大影响,但是对于新版本的JVM很少需要调整。

性能基础

通常,当优化Java应用时,主要关注两个目标中的一个:响应性或吞吐量。
我们将随着教程的进行,重新提及这些概念:

响应性

响应性是应用程序或系统使用请求数据进行响应的速度。
例子包括:

  • 桌面UI响应一个事件有多快
  • 网站返回一个页面有多快
  • 一次数据库查询有多快

关注响应性的应用,较大的暂停时间是不可接受的。因此,重点是在短时间内作出响应。

吞吐量

吞吐量关注在一段特定时间内使应用程序的工作量最大化。吞吐量如何测量的一些示例如下:

  • 给定时间内完成的事务数量
  • 一小时内一个批处理应用可以完成的任务数量
  • 一小时内可以完成的数据库查询次数

对于关注吞吐量的应用,高暂停时间是可以接收的。由于高吞吐量应用程序关注的是较长时间段内的基准,因此不考虑快速响应时间。

G1垃圾回收器

G1垃圾回收器

垃圾优先(G1)垃圾回收器是一个服务器类型的垃圾回收器,使用对象为多处理器、大内存的机器。它在实现高吞吐量的同时,以高概率满足垃圾回收器暂停时间的目标。Oracle JDK7 update 4及以后的版本完全支持G1垃圾回收器。G1垃圾回收器为以下应用设计:

  • 像CMS回收器一样可以与应用程序线程并发操作。
  • 压缩空闲空间而没有漫长的GC导致的暂停时间。
  • 需要更加可预测的GC暂停时间。
  • 不想牺牲大量吞吐量性能。
  • 不需要一个更大的Java堆。

G1计划作为CMS的长期替代品。比较G1与CMS,它们的差异使得G1称为一个更好的解决方案。一个区别是G1是压缩回收器,G1充分压缩,完全避免了使用细粒度空闲列表来进行分配,而是依赖于区域。这大大简化了回收器的部分工作,并且基本消除了潜在的碎片问题。此外,G1比CMS提供了更可预测的垃圾回收暂停,允许用户指定期望的暂停目标。

G1运行概况

旧的垃圾回收器(串行、并行、CMS)都将堆结构分为三部分:年轻代、年老代和以及一个固定内存大小的永久代(译者注:JDK8中,永久代已废弃)。

G1垃圾回收器起步_第3张图片

所有内存对象都在这三个部分中的一个终结。
G1垃圾回收器使用了不同的方法。

G1垃圾回收器起步_第4张图片

堆被划分为一组大小相等的堆区域,每个区域都是一个连续的虚拟内存范围。与旧的回收器中一样,某一区域组被分配为相同的角色(伊甸、幸存、年老),但是它们没有固定的尺寸。这给内存使用提供了更大的灵活性。

当执行垃圾回收时,G1以类似于CMS回收器的方式运行。G1执行一个并发的全局标记阶段,以确定整个堆中对象的活跃度。标记阶段完成之后,G1知道哪些区域大部分是空的。它先在这些区域回收,这样通常会产生大量的空闲空间。这是这种垃圾回收方法被称为Garbage-First(垃圾优先)的原因。顾名思义,G1将它的回收和压缩活动集中于可能充满可回收对象(即垃圾)的堆区域。G1使用一个暂停预测模型来满足用户定义的暂停时间目标并基于指定的暂停时间目标选择回收的区域数量。

G1识别为回收利用成熟的区域使用撤空进行垃圾回收。G1从堆中的一个或者多个区域拷贝对象到一个单独的区域,并在此过程中压缩和释放内存。这种撤空是在多处理器上并行执行的,以降低暂停时间并提升吞吐量。因此,通过每次垃圾回收,G1持续工作来减少碎片,并且在用户定义的暂停时间内工作。这超出了前面两种方法的能力。CMS垃圾回收器不进行压缩。并行年老代垃圾回收只执行整个堆的压缩,这导致相当大的暂停时间。

需要注意的是,G1不是实时回收器。它能大概率的满足设置的暂停时间目标。基于前面回收的数据,G1评估在用户指定的目标时间内可以回收多少个区域。因此,回收器有一个相当精准的回收区域成本的模型,它使用这个模型确定在暂停时间目标内,哪些以及多少区域会回收。

注意:G1既有并发(与应用程序线程一起运行,如净化、标记、清除),又有并行阶段(多线程,例如,stop-the-world)。完整的垃圾回收仍然是单线程的,但是如果调整得当,你的应用程序可以避免完整的垃圾回收。

G1占用空间

如果你从并行年老代回收或者CMS迁移到G1,你可能会看到更大的JVM进程大小。这在很大程度上与“记述(accounting)”数据结构诸如Remembered Set和Collection Set有关。

Remembered Set或RSet跟踪对象引用到一个给定区域。在堆中每个区域一个RSet。RSet使得区域并行独立的回收成为可能。RSet全部的占用空间影响小于5%。

Collection Set或CSet在一次垃圾回收中将要被回收的区域的集合。在垃圾回收过程中,所有CSet中的存活对象被撤空(拷贝/移动)。区域集合可能是伊甸、幸存和/或年老代。CSet对于JVM的大小的影响低于1%。

推荐G1的使用场景

G1的首要关注点是为用户运行那些需要大量堆但垃圾回收延迟有限的应用程序提供一个解决方案。这意味着堆大小为6GB或者更高,并且稳定和可预测的暂停时间低于0.5秒。

现在采用CMS或者并行年老代垃圾回收的应用程序将从切换到G1收益,如果应用程序有以下特征的一个或者多个:

  • 完整垃圾回收的持续时间太长或者太频繁
  • 对象分配率或者晋升(由年轻代移到年老代)率变化显著
  • 不期望的较长的垃圾回收或者压缩暂停(长于0.5到1秒钟)

注意:如果你正使用CMS或者并行年老代垃圾回收,并且你的应用没有经历长时间的垃圾回收暂停,它在当前的垃圾回收器下工作很好。那么,变更为G1垃圾回收器对使用最新版本的JDK并不是个要求。

回顾CMS垃圾回收

回顾代回收和CMS

CMS垃圾回收器(也被称为并发低暂停回收器)回收年老代。它尝试通过与应用程序线程并发执行大部分垃圾回收工作以尽量减少由于垃圾回收导致的暂停。通常并发低暂停垃圾回收器不会拷贝或者压缩存活对象。一次垃圾回收完成而不移动存活对象。如果碎片成为问题,分配一个更大的堆。

注意:CMS垃圾回收器对于年轻代使用与并行垃圾回收器相同的算法。

CMS回收阶段

CMS回收在堆的年老代执行以下阶段:

阶段 描述
(1)初始标记(stop-the-world事件) 年老代中的对象被“标记”为可达,包括那些可能从年轻代可达的对象。相对于minor回收(年轻代)暂停时间,该暂停时间通常持续时间较短。
(2)并发标记 Java应用线程执行的同时,并发遍历年老代可达对象的对象图。从已标记的对象开始扫描,遍历标记所有从根节点可达的对象。修改器(指应用程序?)在2、3、5并发阶段执行,在这些阶段中CMS代分配的任何对象(包括提升的对象)被立即表为存活。
(3)重新标记(Remark)(stop-the-world事件) 查找那些由于Java应用程序线程在并发回收器完成对对象的跟踪之后更新对象而导致并发标记阶段忽略的对象。
(4)并发清除 回收标记阶段识别为不可达的对象。死对象的回收添加这个对象的空间到一个空闲列表,用于后续分配。此刻可能发生死对象合并。注意存活对象不会被移动。
(5)重置 通过清除数据结构,为下一次并发回收做准备。

回顾垃圾回收步骤

接下来,让我们逐步回顾一下CMS垃圾回收器的操作。

1.CMS回收器的堆结构。
堆被分为三部分

G1垃圾回收器起步_第5张图片

年轻代被分为伊甸和两个幸存区。年老代是一个连续空间。对象回收适当的执行,不进行压缩,除非是一次完整垃圾回收。

2.CMS中年轻代如何工作

年轻代的颜色是浅绿色的,年老代是蓝色的。这是应用程序运行一段时间后,CMS可能看起来的样子。对象散布在年老代区域。

G1垃圾回收器起步_第6张图片

对于CMS,年老代对象被合适的释放。它们不会被到处移动。除非有完整的垃圾回收。否则空间不会被压缩。

3.年轻代回收

存活对象从伊甸区和幸存区拷贝到另一个幸存区。已经到达年龄阈值的所有较老的对象被提升到年老代。

G1垃圾回收器起步_第7张图片

4.年轻代回收之后

年轻代回收之后,伊甸区被清空并且一个幸存区也被清空。

G1垃圾回收器起步_第8张图片

新提升到年老代的对象在图中以深蓝色显示。绿色对象是还未提升到年老代的幸存的年轻代对象。

5.CMS中年老代回收

两次stop-the-world事件发生:初始标记和再次标记。当年老代达到某个占有率,CMS开始。

G1垃圾回收器起步_第9张图片

(1)初始标记是一个短暂暂停阶段,此时存活对象(可达)被标记。
(2)当应用程序继续执行时,并发标记查找存活对象。
(3)再次标记阶段,查找前面的(2)并发标记阶段遗漏的对象。

6.年老代回收——并发清除

在前一阶段中未被标记的对象释放。没有压缩。

G1垃圾回收器起步_第10张图片

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

7.年老代回收——清除之后

在(4)清除阶段之后,你可以看到很多内存被释放了。你还要注意到没有进行压缩。

G1垃圾回收器起步_第11张图片

最后,CMS回收器将移到(5)重置阶段,等待下一次达到垃圾回收阈值。

G1垃圾回收器逐步说明

G1垃圾回收器逐步说明

G1垃圾回收器采用了不同的方法来分配堆。下面的图片按步回顾了G1系统。

1.G1堆结构

堆是一个内存区,拆分成许多固定大小的区域。

G1垃圾回收器起步_第12张图片

区域大小在启动时由JVM选择。JVM目标一般是2000个左右的区,通过从1MB到32MB调整区域大小。

2.G1堆分配

实际上,这些区域被映射为伊甸、幸存和年老代空间的逻辑表示。

G1垃圾回收器起步_第13张图片

图片中的颜色展示了哪个区域与哪个角色关联。存活对象从一个区撤空到另一个区(即复制或移动)。区域被设计为并行的回收,停止或者不停止所有其它应用程序线程。

如图所示,区域可能被分配到伊甸、幸存和年老代区。此外,还有第四种类型的对象,称为巨大区。这些区域被设计为持有大小为标准区域50%或者更大的对象。它们被存储为一组相邻的区域。最后一种类型的区域是堆中未使用的区域。

注意:在写这篇文章时,回收巨大对象无法优化。因此,应避免创建这种大小的对象。

3.G1中的年轻代

堆被分隔成大概2000个区域。最小大小为1MB,最大大小为32MB。蓝色区域持有年老代对象,绿色区域持有年轻代对象。

注意,区域不必像旧的垃圾回收器那样是连续的。

4.G1中的一次年轻代回收

存活对象被撤空(即如复制或移动)到一个或者多个幸存区域。如果到达年龄阈值,这些对象中的一些将提升到年老代区域。

G1垃圾回收器起步_第14张图片

5.G1中一次年轻代回收的结束

存活对象已经被撤空到幸存区域或者年老代区域。

G1垃圾回收器起步_第15张图片

最近提升的对象显示为深蓝色。幸存区域为绿色。

总之,关于G1中的年轻代,可以说明一下几点:

  • 堆是一个单独的内存空间,被拆分成了区域。
  • 年轻代内存由一组非连续的区域组成。这使得它在需要时非常容易调整。
  • 年轻代垃圾回收是stop-the-world事件。所有应用程序线程都停止。
  • 年轻代垃圾回收使用多线程并行进行。
  • 存活对象被拷贝到新的幸存区或者年老代区域。

G1中的年老代回收

类似于CMS回收器,对于年老代对象,G1回收器被设计成一个低暂停回收器。下表描述了在年老代G1回收的阶段。

G1回收阶段——并发标记周期阶段

G1回收器在堆的年老代执行以下阶段。注意某些阶段是年轻代回收的一部分。

阶段 描述
(1)初始标记(stop-the-world事件) 这是一个stop-the-world事件。对于G1,它是利用一个正常的年轻代回收。标记幸存区域(根区域),这些区域可能对年老代中的对象有引用
(2)根区域扫描 扫描幸存区域,查找对年老代的引用。它在应用程序持续运行时发生。该阶段必须在一次年轻代回收可能发生之前完成。
(3)并发标记 在整个堆查找存活对象。它在应用程序持续运行时发生。该阶段可能被年轻代垃圾回收打断。
(4)再次标记(stop-the-world事件) 完成堆中存活对象的标记。使用一种称为snapshot-at-the-beginning(STAB)算法,它比CMS回收器中使用的算法快得多。
(5)清除(stop-the-world事件并且并发) 执行存活对象的记述(accounting)并且完全释放区域(stop-the-world);擦掉RSet(stop-the-world);重置空的区域并将它们返回到空闲列表(并发)。
(*)复制(stop-the-world) stop-the-world暂停来撤空或复制存活对象到一个未使用的区域。这可能在年轻代区域完成,记录日志为[GC pause(young)]。或者在年轻代和年老代两类区域完成,记录日志为[GC Pause(mixed)]

G1年老代回收逐步说明

根据定义的阶段,让我们看一下在G1回收器中它们是如何与年老代交互的。

6.初始标记阶段

存活对象的初始标记利用了一次年轻代垃圾回收。在日志中,它注释为 GC pause (young)(inital-mark)

G1垃圾回收器起步_第16张图片

7.并发标记阶段

如果找到空的区域(标为“X”),它们在“再次标记阶段”被立即移除。此外,确定活跃度的“记述(accounting)”信息也被计算出来。

G1垃圾回收器起步_第17张图片

8.再次标注阶段

空区域被移除和回收。现在计算所有区域的区域活跃度。

G1垃圾回收器起步_第18张图片

9.复制/清除阶段

G1选择活跃度最低的区域,这些区域可以最快被回收。然后这些区域和年轻代回收同时被回收。这在日志中表示为[GC pause (mixed)]。因此年轻代和年老代两者同时被回收。

G1垃圾回收器起步_第19张图片

10.复制/清除阶段之后

被选择的区域已经被回收和压缩到深蓝色区域和深绿色区域,如图中所示。

G1垃圾回收器起步_第20张图片

年老代回收概述

总之,关于年老代垃圾回收有以下几个关键点:

  • 并发标记阶段
    • 活跃度信息在应用程序运行时并发计算
    • 活跃度信息确定哪些区域最适合于在撤空暂停阶段(evacuation pause)被回收
    • 没有像CMS的全面清扫(sweeping)阶段
  • 再次标记阶段
    • 使用snapshot-at-the-beginning(SATB)算法,比CMS中使用的算法更快
    • 完全空区域被回收
  • 复制/清除阶段
    • 年轻代和年老代同时被回收
    • 年老代区域根据它们的活跃度选择

命令行选项和最佳实践

命令行选项和最佳实践

在本节,让我们看一下G1的各种命令行选项。

基本命令行选项

启用G1垃圾回收器使用:-XX:+UseG1GC

下面是启动下载的JDK demo和sample中包含的Java2Demo的一个示例命令行:

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将尽最大努力来达到它。因此,暂停时间目标有时会无法满足。默认为200毫秒。

-XX:InitiatingHeapOccupancyPercent=45 启动一次并发垃圾回收周期时,整个堆的占用百分比。它被G1用来触发一次并发垃圾回收周期,基于整个堆的占用比,而不仅是某一个代。0标识“进行持续的垃圾回收周期”。默认值为45(即45%满或者占用)

最佳实践

使用G1时,你应该遵循一些最佳实践。

不要设置年轻代大小

显式的通过-Xmn设置年轻代大小会干涉G1垃圾回收器的默认行为。

  • G1将不再关注回收的暂停时间目标。所以本质上,设置年轻代大小会禁用暂停时间目标。
  • G1不能再根据需要扩展或者收缩年轻代空间。因为尺寸是固定的,所以不能改变。

响应时间指标

不要使用平均响应时间(ART)作为度量来设置XX:MaxGCPauseMillis=,而是考虑将值设置为满足90%的时间甚至更多的目标。这意味着90%的请求用户将不会体验到比目标更高的响应时间。记住,暂停时间是一个目标,并不能保证总是被满足。

什么是撤空失败?

当JVM在垃圾回收期间由于幸存或者提升对象耗尽堆区域时,会发生提升失败。堆由于已经最大,所以不能扩展。当使用了-XX:+PrintGCDetails时这会通过目的空间溢出(to-space overflow)体现在垃圾回收日志中。这很昂贵!

  • 垃圾回收仍然需要继续,因此空间必须被释放。
  • 未成功复制的对象必须适当的终结。
  • 对CSet中区域的RSet的任何更新必须再生。
  • 所有这些步骤都很昂贵。

如何避免撤空事变

为了避免撤空失败,考虑以下选项。

  • 增加堆内存
    • 增加-XX:G1ReservePercent=n,默认为10。
    • G1通过设法保留备用内存空闲来创建一个假上限,以防更多的"to-space"请求。
  • 较早启动标记周期
  • 使用-XX:ConcGCThreads=n选项增加标记线程的数目

G1垃圾回收开关的完整列表

下面是G1垃圾回收开关的完整列表。记住使用上面列出的最佳实践。

选项和默认值 描述
-XX:+UseG1GC 使用垃圾优先(G1)垃圾回收器
-XX:MaxGCPauseMillis=n 设置一个最大GC暂停时间的目标。这是一个软性目标,JVM将尽最大努力来达到它。
-XX:InitiatingHeapOccupancyPercent=n 启动一次并发垃圾回收周期时,整个堆的占用百分比。它被G1用来触发一次并发垃圾回收周期,基于整个堆的占用比,而不仅是某一个代。0标识“进行持续的垃圾回收周期”。默认值为45。
-XX:NewRatio=n 年轻代和年老代大小的比例。默认为2。
-XX:SurvivorRatio=n 伊甸和幸存空间大小的比例。默认为8。
-XX:MaxTenuringThreshold=n 期限阈值的最大值。默认值为15。
-XX:ParallelGCThreads=n 垃圾回收器并行阶段使用的线程数目。默认值随JVM运行的平台而异。
-XX:ConcGCThreads=n 并发垃圾回收器使用的线程数目。默认值随JVM运行的平台而异。
-XX:G1ReservePercent=n 作为虚假上限而保留的堆的数目,以减少提升失败的可能性。默认值为10。
-XX:G1HeapRegionSize=n 对于G1,Java堆被分为大小相同的区域。该选项设置各区域的大小。它的默认值基于堆的大小确定,符合人类工程学。最小值为1MB,最大值为32MB。

使用G1时的垃圾回收日志

使用G1时的垃圾回收日志

我们需要讨论的最后一个主题是使用日志信息分析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。
  • 显示伊甸、幸存和总的堆内存占用。

示例输出

[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]

确定时间

一组开关用于确定在垃圾回收日志中时间如何显示。

(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日志

为了理解日志,本节使用实际的垃圾回收日志输出定义了若干术语。以下示例显示了日志中的输出,以及你将在日志中找到的术语和值的解释。

G1日志术语索引

  • Clear CT
  • CSet
  • External Root Scanning
  • Free CSet
  • GC Worker End
  • GC Worker Other
  • Object Copy
  • Other
  • Parallel Time
  • Ref Eng
  • Ref Proc
  • Scanning Remembered Sets
  • Termination Time
  • Update Remembered Set
  • Worker Start

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 — worker启动的时间戳。

注意:日志基于线程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 — 用于扫描外部根的时间(如像指向堆的系统字典之类的东西)

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 — 任何已完成但尚未在暂停开始之前由并发净化线程处理的缓冲区都必须更新。时间依赖于卡片的密度,卡片越多,花费时间越长。

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 — 查找指向CSet的指针。

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]

对象复制 — 每个独立线程复制和撤空对象花费的时间。

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 — 当一个工作线程完成其特定对象集合的复制和扫描时,它将进入终止协议。它查找需要悄悄执行的工作,一旦完成该工作,它再次进入终止协议。“终止”尝试统计所有对执行工作的尝试。

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 – 当一个独立的垃圾回收Worker停止时的时间戳。

GC worker time – 一个独立的垃圾回收Worker线程花费的时间。

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 —不能归于前面列出的worker阶段的时间(对于每个线程)。应该相当低。在过去,我们曾经看到过极度高的值,它们被归于JVM其它部分的瓶颈(如,随着Tiered,代码缓存(Code Cache)占用的增加)。

Clear CT

[Clear CT: 0.6 ms]

用于清除RSet扫描元数据卡片表的时间。

Other

[Other: 6.8 ms]

垃圾回收暂停的其它顺序阶段花费的时间。

CSet

[Choose CSet: 0.1 ms]

最后确定要回收的区域集合所花费的时间。通常非常小;当必须选择年老代时,稍微长一些。

Ref Proc

[Ref Proc: 4.4 ms]

处理soft、weak等花费的时间。自垃圾回收先前阶段推迟的引用。

Ref Enq

[Ref Enq: 0.1 ms]

Time spent placing soft, weak, etc. references on to the pending list.
花费在放置soft、weak等的时间。在待定列表上的引用。

Free CSet

[Free CSet: 2.0 ms]

释放刚才回收的区域集花费的时间,包括它们的RSet。

总结

在本OBE,你获得了Java JVM中包含的G1垃圾回收器的一个概述。首先,你学会了堆和垃圾回收器是任何Java JVM中的关键部分。接下来,你回顾了CMS和G1的垃圾回收是如何工作的。然后,你了解了G1命令行开关和使用它们的最佳实践。最后,你了解了包含在垃圾回收日志中的对象和数据。

本教程中,你学会:

  • Java JVM的组件
  • G1垃圾回收器概述
  • CMS垃圾回收器回顾
  • G1垃圾回收器回顾
  • 命令行开关和最佳实践
  • G1中的日志

资源

对于更多信息,请查看以下网址和链接。

  • Java HotSpot VM选项
  • G1垃圾回收器
  • Poonam Bajaj G1 GC Blog Post
  • Java SE 7:Develop Rich Client Applications
  • Java性能 — Charlie和Binu John
  • Oracle学习库

Credits

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

你可能感兴趣的:(JVM)