JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿

CMS垃圾回收机制 参考:图解 CMS 垃圾回收机制原理,-阿里面试题

CMS与G1的区别 参考:CMS收集器和G1收集器优缺点

写这篇文章是基于阿里面试官的一个问题:众所周期,G1跟其他的垃圾回收算法差别很大,你了解G1的垃圾回收架构吗?为什么G1可以做到回收时间用户可以设定?

G1垃圾回收器其实是JDK7的特性,在目前JDK10都已经发布的情况下,已经不是什么新特性了,而我到它现在才关注它,可见我是有多么的懒;而我终于关注它了,可见我的懒还算是有救的 :)

G1其实是Garbage First的意思,垃圾优先? 不是,是优先处理那些垃圾多的内存块的意思。在大的理念上,它还是遵循JVM的内存分代假设(其实叫假设不准确,这是从实际Java应用的内存使用观察得到的结论):

90%的对象熬不过第一次垃圾回收,而老的对象(经历了好几次垃圾回收的对象)则有98%的概率会一直活下来。

基于这个分代假设,一般的垃圾回收器把内存分成三类: Eden(E), Suvivor(S)和Old(O), 其中Eden和Survivor都属于年轻代,Old属于老年代,新对象始终分配在Eden里面,熬过一次垃圾回收的对象就被移动到Survisor区了,经过数次垃圾回收之后还活着的对象会被移到Old区。JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第1张图片

一般GC的内存分布

这样分代的好处是,把一个复杂的大问题,分成两类不同的小问题,针对不同的小问题,采用更有针对性的措施(分而治之):

  • 对于年轻代的对象,由于对象来的快去得快,垃圾收集会比较频繁,因此执行时间一定要短,效率要高,因此要采用执行时间短,执行时间的长短只取决于对象个数的垃圾回收算法。但是这类回收器往往会比较浪费内存,比如Copying GC,会浪费一半的内存,以空间换取了时间。
  • 对于老年代的对象,由于本身对象的个数不多,垃圾收集的次数不多,因此可以采用对内存使用比较高效的算法。

跟其它垃圾回收器不一样的是:G1虽然也把内存分成了这三大类,但是在G1里面这三大类不是泾渭分明的三大块内存,G1把内存划分成很多小块, 每个小块会被标记为E/S/O中的一个,可以前面一个是Eden后面一个就变成Survivor了。

 

JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第2张图片

G1的内存分布

这么做给G1带来了很大的好处,由于把三块内存变成了几百块内存,内存块的粒度变小了,从而可以垃圾回收工作更彻底的并行化。

G1的并行收集做得特别好,我们第一次听到并行收集应该是CMS(Concurrent Mark & Sweep)垃圾回收算法, 但是CMS的并行收集也只是在收集老年代能够起效,而在回收年轻代的时候CMS是要暂停整个应用的(Stop-the-world)。而G1整个收集全程几乎都是并行的,它回收的大致过程是这样的:

  • 在垃圾回收的最开始有一个短暂的时间段(Inital Mark)会停止应用(stop-the-world)
  • 然后应用继续运行,同时G1开始Concurrent Mark
  • 再次停止应用,来一个Final Mark (stop-the-world)
  • 最后根据Garbage First的原则,选择一些内存块进行回收。(stop-the-world)

由于它高度的并行化,因此它在应用停止时间(Stop-the-world)这个指标上比其它的GC算法都要好。

G1的另一个显著特点他能够让用户设置应用的暂停时间,为什么G1能做到这一点呢?也许你已经注意到了,G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如。 (阿里面试)

由于内存被分成了很多小块,又带来了另外好处,由于内存块比较小,进行内存压缩整理的代价都比较小,相比其它GC算法,可以有效的规避内存碎片的问题。

说了G1的这么多好处,也该说说G1的坏处了,如果应用的内存非常吃紧,对内存进行部分回收根本不够,始终要进行整个Heap的回收,那么G1要做的工作量就一点也不会比其它垃圾回收器少,而且因为本身算法复杂了一点,可能比其它回收器还要差。因此G1比较适合内存稍大一点的应用(一般来说至少4G以上),小内存的应用还是用传统的垃圾回收器比如CMS比较合适。

总结

G1通过在垃圾回收领域应用并行化的策略,把几块大内存块的回收问题,变成了几百块小内存的回收问题,使得回收算法可以高度并行化,同时也因为分成很多小块,使得垃圾回收的单位变成了小块内存,而不是整代内存,使得用户可能对回收时间进行配置,垃圾回收变得可以预期了。

分而治之、化整为零这些朴素的架构思想往往是很多牛叉技术产品背后的思想根源啊。

 

参考:G1垃圾回收器

 

总结这篇文章和其他的资料,G1可以基本稳定在0.5s到1s左右的延迟,但是并不能保证更低的比如毫秒级(金融场景,所以说涉及到钱的,对技术要求真高),号称zing可以(但是一般做到低延时,在其他方面肯定有所损耗,比如吞吐),但是没有实际去研究过这种。另外,G1也可能和CMS一样出现Full GC,如果区域不够提升的话,所以它一般用于需求更大的堆中。但G1最显著于CMS的,在于它对空间做了整理,这样减少了空间的碎片化。CMS的空间碎片话相较于G1要严重很多,试想下它的Mark-Sweep之后的空间,有很多小碎片,但是都比要分配的小,然后触发一次Full GC,简直了。再说点杂的,G1的思想,感觉有点像Java CocurrentHashMap,也是将一个大的分成若干个Region,然后再处理

G1 垃圾收集器入门

概览


目的

这个教程覆盖了如何使用G1垃圾收集器和它是怎样被Hotspot JVM使用的,你会学到G1收集器内部是如何工作的,使用G1时的一些关键命令行开关和记录它的操作的一些选项。

完成耗时

大约1小时

介绍

这个OBE(Oracle By Example)覆盖了Java里的Java虚拟机G1垃圾回收的基本概念,在OBE的第一部分, 
在介绍垃圾收集器和性能时会附带提供JVM的概览。下一部分回顾一下Hotspot JVMCMS收集器如何工作。然后,一步一步来指导使用Hotspot JVMG1垃圾回收,跟着,会用一段来讲G1垃圾回收器的可用的命令行选项。最后,你会学到G1回收器的日志选项。

硬件、软件需求

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

  • 一台运行Windows XP或者更高版本的PC,Mac OS X或者Linux。注意已在Windows 7系统亲测,还没有在所有平台测试。然而,应该会在OS X或Linux上正常运行。多处理器核心的机器更好。
  • Java 7 Update 9或更高版本
  • 最新Java 7 演示和示例压缩文件

先决条件

开启教程之前,你应该:

  • 如果你没有这样做,下载安装最新版本的JDK,Java 7 JDK Downloads。
  • 从同样的地址下载安装演示示例压缩文件,解压文件到一个目录中,比如:C:/javademos

Java技术和虚拟机


Java概览

Java是Sun微系统公司在1995年首次发布的一个编程语言和计算平台。它是支撑Java程序包括工具、游戏、商业应用的底层技术。在全球Java运行在超过8亿5千万台个人电脑上,数以十亿计的设备上,包括移动、电视设备。Java由一些关键组件组成,作为一个整体,创建Java平台。

Java运行时

当你下载Java,你获得Java运行环境(JRE,Java Runtime Environment)。 JRE由Java虚拟机(JVM)、Java平台核心类和支撑Java平台的库组成。要在你电脑上运行Java应用,所有的这些都是必须的。有了Java 7, Java应用从操作系统方面来说是作为桌面应用运行的,作为一个桌面应用但是从网络上安装需要使用Java Web Start,或者在浏览器里作为一个Web嵌入式的应用(使用JavaFX

Java编程语言

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

  • 平台独立 - Java应用被编译成存储在类文件中的字节码,在JVM里被加载。一旦应用在JVM里运行,它们可以运行在许多不同的操作系统和设备上。
  • 面向对象 - Java是一个面向对象的语言,借鉴了C和C++的诸多特性,在它们之上改进。
  • 自动垃圾收集 - Java自动分配和释放内存,所以程序不必背负这个任务。
  • 丰富的标准库 - Java包括大量预先做好的对象,它用被用在执行比如输入/输出、网络、数据操作这样的任务上。

Java开发工具箱

Java开发工具箱(JDK,Java Development Kit)是一个开发Java应用的工具集。有了JDK,你可以编译用Java语言编写的应用程序,在JVM里面运行它们。另外,JDK提供工具打包和发布你的应用。

JDK和JRE分享同样的Java应用编程接口(Java API,Java Application Programming Interfaces)。Java API是已经打包的库的集合,开发者用来创建Java应用。Java API通过提供工具完成很多通常的编程任务包括字符串操作、时间日期处理、网络和实现数据结构(比如:列表、映射、栈和队列)使开发更加容易。

Java虚拟机

Java虚拟机(JVM,Java Virtual Machine)是一个抽象的计算机器,Java虚拟机是一个程序,对在它里面运行的编写的程序来说, 看起来像一个机器。这样,Java程序就会用相同的接口和库来编写。每一个针对特定操作系统的JVM实现,把Java程序指令翻译成运行在本地操作系统的指令和命令。这样,Java程序实同了平台独立。

Sun微系统公司完成了第一个Java虚拟机的原型实现,仿真Java虚拟机指令集设置进一个被类似当时的个人数码助手(PDA,Personal Digital Assistant)手持设备的托管的软件里。Oracle现在实现仿真Java虚拟机在移动、桌面和服务器设备上,但是Java虚拟机没有承担任何具体的技术实现,管理硬件或者管理操作系统。It is not inherently interpreted, but can just as well be implemented by compiling its instruction set to that of a silicon CPU. It may also be implemented in microcode or directly in silicon斜体这里翻译不清楚,请大神指点。好像涉及解释执行、指令集、微码等

Java虚拟机对Java编程语言一概不知,只知道一个特定的二进制格式,就是类文件格式,一个类文件包含Java虚拟机指令(或者叫字节码)和一个符号表,和一些辅助信息。

为了达到安全的目的,Java虚拟机在类文件代码上利用强大的语法和结构化的约束条件。然而任何函数性语言可以依照一个可用的被Java虚拟机托管类文件来表达。被通用的、机器平台独立性吸引,其它语言的实现者可以把Java虚拟机视为其它语言的递送载体,(1)The Java Virtual Machine

探索Java虚拟机架构

Hotspot架构

Hotspot虚拟机拥有一个架构,它支持强大特性和能力的基础平台,支持实现高性能和强大的可伸缩性的能力。举个例子,Hotspot虚拟机JIT编译器生成动态的优化,换句话说,它们在Java应用执行期做出优化,为底层系统架构生成高性能的本地机器指令。另外,经过它的运行时环境和多线程垃圾回收成熟的进化和连续的设计, Hotspot虚拟机在高可用计算系统上产出了高伸缩性。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第3张图片
Java虚拟机的主要组件,包括类加载器、运行时数据区和执行引擎

Hotspot关键组件

Java虚拟机有关性能的关键组件已经在下面的图片上高亮显示了。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第4张图片
Java虚拟机有三个组件关注着什么时候进行性能优化,堆空间是你的对象所存储的地方,这个区域 
被启动时选择的垃圾回收器管理,大部分调优选项与调整堆大小和根据你的情况选择最适当的垃圾收集器相关。即时编译器对性能也有很大的影响,但是使用新版本的Java虚拟机时很少需要调整。

性能基础

典型的,当调优一个Java应用时,把焦点放在两个主要的目标上:响应能力或者吞吐量。随着教程的进行我们会再重新提及这些概念。

响应能力

响应能力指的是一个应用回应一个请求数据的速度有多快。示例包括:

  • 桌面UI响应事件的速度
  • 网站返回网页的速度
  • 数据查询返回的速度 
    对关注响应能力的应用来说,长暂停时间是不可接受的,重点是在短的时间周期内能做出响应。

吞吐量

吞吐量关注在特定的时间周期内一个应用的工作量的最大值。举例如何衡量吞吐量,包括:

  • 给定时间内完成事务的数量
  • 一小时内批处理程序完成的工作数量
  • 一小时内数据查询完成的数量 
    对关注吞吐量的应用来说长暂停时间是可以接受的。由于高吞吐量的应用关注的基准在更长周期时间上,所以快速响应时间不在考虑之内。

G1垃圾回收器


G1垃圾回收器

Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持G1垃圾收集器。G1垃圾回集器为以下应用设计:

  • 类似CMS收集器,可以和应用线程同时并发的执行
  • 压缩空闲空间时没有GC引起的暂停时间
  • 需要更可预言的GC暂停时间
  • 不想牺牲大量的吞吐量性能
  • 不需要特别大的Java堆

G1垃圾收集器计划长期替换并发标记清除收集器(CMS,Concurrent Mark-Sweep Collector)。G1和CMS比较,有一些不同点让G1成为一个更好的解决方案。一个不同点是G1是一个压缩收集器。G1收集器充分地压缩空间以完全避免为分配空间使用细粒度的空闲列表,而不是依赖于区块。这相当简化了收集器的部件,和尽量消除可能的碎片问题。同时,G1收集器相比CMS收集器而方言,提供更可预言的垃圾收集暂停时间,允许用户指定想要暂停时间指标。

G1收集器操作概览

旧的垃圾收集器(串行的:serial,并行的:parallel,并发标记清除:CMS)都把堆结构化为三个部分:年轻代、年老代和固定大小的永久代。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第5张图片
所以内存对象最终都在这三个区域里。 
G1收集器应用了一个不同的方法。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第6张图片
堆空间被分割成一些相同大小的堆区域,每一个都是连续范围的虚拟内存。特定的区域集合像旧的收集器一样被指派为相同的角色(伊甸:eden、幸存:survivor、年老:old),但是它们没有一个固定大小。这在内存使用上提供了更强大的灵活性。

当执行垃圾收集时,G1收集器以与CMS收集器类似的方式操作。G1收集器执行一个全局的并发标记阶段来决定堆中的对象的活跃度。之后标记阶段就完成了。G1收集器知道哪个区域基本上是空的。它首先会收集那些产出大量空闲空间的区域。这就是为什么这个垃圾收集的方法叫做垃圾优先的原因。就像名称显示的那样,G1收集器集中它的收集和压缩活动在堆里的那些可完全被回收的区域,那就是垃圾。G1收集器使用一个暂停预言的模式去达到一个用户定义的暂停时间指标,基于用户指定的暂停时间指标去选择收集区域的数量。

被G1收集器鉴定为可以回收的区域就是垃圾,使用抽空的方式收集。G1收集器从堆空间的一个或多个区域里复制对象到堆空间的一个单独的区域内,这个过程中同时压缩和释放内存。这个抽空过程在多处理上以并行的方式运行,以减小暂停时间和增加吞吐量。因此,每一次垃圾收集G1收集器连续不断地去减少碎片,在用户指定的暂停时间内工作。这超越了以往方法的能力。并发标记-清除(CMS,Concurrent Mark Sweep)垃圾收集器不做压缩操作。并行年老代(ParallelOld)垃圾收集只进行整个堆的压缩,会导致相当大的暂停时间。

注意: G1收集器不是实时的收集器非常重要。它在很大程度上符合用户设定的暂停时间指标但是并不绝对符合。基于前面垃圾收集的数据来看,G1收集器会估算在用户指定的时间指标能收集多少区域。因此,收集器有一个合理的精确的收集这些区域的代价模型,它使用这个模型决定在用户指定的暂停时间内收集哪些、多少个区域。

注意: G1收集器同时有并发(和应用线程一起运行,比如,提炼、标记、清理)和并行(多线程,比如,stop the world)两个阶段。全量垃圾回收仍然是单线程的,但是如果调优的适当你的应用应该会避免全量垃圾回收。

G1回收器足迹

如果你从ParallelOldGc或者CMS收集器迁移到G1收集器,你很有可能会看到一个大的Java虚拟机进程大小,这和审计”数据结构比如已记忆集合(Remembered Sets)和收集集合(Collection Sets)有很大关系“。

Remembered Sets或者RSets把对象引用推进一个给定的区域。在堆空间中每一个区有一个RSet。RSet允许一个区域并行的、独立的收集。RSet总体的足迹影响小于5%。

Collection Sets或者CSets,是在垃圾回收过程中会被回收的区域集合。在RSet中的所有活跃对象在垃圾回收过程中会被抽空(复制/移动)。集合包含的区域可以是eden、survivor或者年老代。CSets在Java虚拟机大小的影响小于1%。

建议使用G1收集器的场景

G1收集器首要关注的是为用户运行着需要大堆空间、限制的垃圾回收延迟的应用提供一个解决方案。这意味着堆大小为6GB左右或者更大,稳定的、可预言的暂停时间小于0.5秒。

如果应用有以下一个或多个特点,当下运行着CMS或ParallelOldGC垃圾收集器的应用把收集器切换到G1收集器的话,会从中受益的:

  • Full GC持续时间太长或者太频繁
  • 对象分配比率或者提升有显著的变化
  • 不期望的长时间垃圾收集或者压缩暂停(大于0.5到1秒)

注意:如果你在使用CMS或者ParallenOldGC收集器,你的应用不曾经历过长时间的垃圾收集暂停,保持使用你当前的收集器比较好。在使用最新的JDK的情况下,改变到G1收集器不是一个必要的事情。

回顾CMS垃圾回收


回顾分代垃圾回收和CMS

并发标记清除(CMS)收集器(也叫并发低延迟收集器)回收年老代垃圾。它通过和应用线程并发的执行大部分垃圾收集工作的方式来尝试最小化垃圾回收引起的暂停。正常情况下并发低延迟收集器不会复制或者压缩活跃对象。一次垃圾收集的完成不必移动活跃对象。如果内存碎片成为一个问题,分配更大的堆空间。

注意: CMS收集器在年轻代上使用和并行收集器相同的算法。

CMS收集阶段

CMS收集器在堆的年老代空间上执行以下阶段:

阶段 描述
(1)初始标记(Stop the World事件) 年老代里的对象被标记为可达的包括那些可能从年轻代可达的对象。此期间暂停时间相对minor gc的暂停时间是比较 短的
(2)并发标记 当Java应用线程运行时,并发的遍历年老代对象图可达的对象。从标记的对象和根上可达到标记对象开始扫描。设值方法在并发的2、3、5阶段期间执行,在这些阶段(包括晋升的对象)被分配进CMS代所有对象都会立刻被标记为活跃对象。
(3)重新标记(Stop the World事件) 寻找那些在并发标记阶段丢失的,在并发收集器完成之后跟踪对象之后由Java应用线程的更新的对象。
(4)(并发清除) 收集在标记阶段被鉴定为不可达的对象。收集死亡对象会增加空闲列表的空间,方便之后的对象分配。聚合死亡对象可以会在此点发生。注意活跃对象是不会被移动。
(5)(重新设置) 清理数据结构为下一次并发收集做准备

回顾垃圾收集步骤

接下来,让我们一步一步的回顾下CMS收集器的操作步骤

1. CMS收集器堆结构

堆空间被分割为三块空间。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第7张图片
年轻代分割成一个Eden区和两个Survivor区。年老代一个连续的空间。就地完成对象收集。除非有FullGC否则不会压缩。

2.CMS年轻代垃圾收集如何工作

年轻代被标为浅绿色,年老代被标记为蓝色。如果你的应用已经运行了一段时间,CMS的堆看起来应该是这个样子。对象分散在年老代区域里。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第8张图片
使用CMS,年老代对象就地释放。它们不会被来回移动。这个空间不会被压缩除非发生FullGC。

3.年轻代收集

从Eden和Survivor区复制活跃对象到另一个Survivor区。所有达到他们的年龄阈值的对象会晋升到年老代。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第9张图片

4.年轻代回收之后

一次年轻代垃圾收集之后,Eden区和其中一个Survivor区被清空。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第10张图片
最近晋升的对象以深蓝色显示在上图中,绿色的对象是年轻代幸免的还没有晋升到老年代对象。

5.CMS的年老代收集

发生两次stop the world事件:初始标记和重新标记。当年老代达到特定的占用比例时,CMS开始执行。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第11张图片
(1)初始标记是一个短暂暂停的、可达对象被标记的阶段。(2)并发标记寻找活跃对象在应用连续执行时。最后,在(3)重新标记阶段,寻找在之前并发标记阶段中丢失的对象。

6.年老代收集-并发清除

在之前阶段没有被标记的对象会被就地释放。不进行压缩操作。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第12张图片
注意:未被标记的对象等于死亡对象

7.年老代收集-清除之后

(4)清除阶段之后,你可以看到大量内存被释放。你还可以注意到没有进行压缩操作。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第13张图片
最后,CMS收集器会走过(5)重新设置阶段,等待下一次垃圾收集时机的到来。

循序渐进G1垃圾收集器


循序渐进G1垃圾收集器

G1收集器在分配堆空间的方法上有些不同。下面的图片一步一步系统的回顾G1收集器。

1.G1堆结构

堆空间是一个被分成许多固定大小区域的内存块。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第14张图片
Java虚拟机启动时选定区域大小。Java虚拟机通常会指定2000个左右的大小相等、每个大小范围在1到32M的区域。

2.G1堆空间分配

实际上,这些区域被映射成Eden、Survivor、年老代空间的逻辑表述形式。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第15张图片
图片中的颜色表明了哪个区域被关联上什么角色。活跃对象从一个区域疏散(复制、移动)到另一个区域。区域被设计为并行的方式收集,可以暂停或者不暂停所有的其它用户线程。

明显的区域可以被分配成Eden、Survivor、Old区域。另外,有第四种类型的区域叫做极大区域(Humongous regions)。这些区域被设计成保持标准区域大小的50%或者更大的对象。它们被保存在一个连续的区域集合里。最后,最后一个类型的区域就是堆空间里没有使用的区域。

注意:写作此文章时,收集极大对象时还没有被优化。因此,你应该避免创建这个大小的对象。

3.G1的年轻代

堆空间被分割成大约2000个区域。最小1M,最大32M,蓝色区域保持年老代对象,绿色区域保持年轻代对象。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第16张图片
注意:区域没有必要像旧的收集器一样是保持连续的。

4.G1的年轻代收集

活跃对象会被疏散(复制、移动)到一个或多个survivor区域。如果达到晋升总阈值,对象会晋升到年老代区域。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第17张图片
这是一个stop the world暂停。为下一次年轻代垃圾回收计算Eden和Survivor的大小。保留审计信息有助于计算大小。类似目标暂停时间的事情会被考虑在内。

这个方法使重调区域大小变得很容易,按需把它们调大或调小。

5.G1年轻代回收的尾声

活跃对象被疏散到Survivor或者年老代区域。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第18张图片
最近晋升的对象显示为深蓝色。Survivor区域显示为绿色。

关于G1的年轻代回收做以下总结:

  • 堆空间是一块单独的内存空间被分割成多个区域。
  • 年轻代内存是由一组非连续的区域组成。这使得需要重调大小变得容易。
  • 年轻代垃圾回收是stop the world事件,所有应用线程都会因此操作暂停。
  • 年轻代垃圾收集使用多线程并行回收。
  • 活跃对象被复制到新的Survivor区或者年老代区域。

G1年老代垃圾回收

类似CMS收集器,G1收集器为年老代对象被设计成一个低暂停收集器。下面的表描述了在年老代上的G1收集阶段。 
G1垃圾收集器在堆上的年老代执行以下阶段。注意一些阶段是年轻代回收的一部分。

阶段 描述
(1)初始标记(stop the world事件) 这是一个stop the world事件,使用G1回收器,背负着一个常规的年轻代收集。标记那些有引用到年老代的对象的survivor区(根区)
(2)根区扫描 为到年老代的引用扫描survivor区,这个发生在应用继续运行时。这个阶段在年轻代收集前必须完成
(3)并发标记 遍历整个堆寻找活跃对象,这个发生在应用运行时,这个阶段可以被年轻代垃圾回收打断。
(4)重新标记(stop the world事件) 完全标记堆中的活跃对象,使用一个叫作snapshot-at-the-beginning(SATB)的比CMS收集器的更快的算法
(5)清理(stop the world事件和并发) 在活跃对象上执行审计操作和释放区域空间(stop the world);净化已记忆集合(stop the world);重置空间区域和返回它们到空闲列表(并发)
(*)复制(stop the world事件) 这些是stop the world暂停为了疏散或者复制活跃对象到新的未使用的区域。这个可以由被记录为[GC Pause (young)]的年轻代区域或者被记录为[GC Pause (mixed)]年轻代和年老代区域完成

循序渐进G1年老代垃圾回收

记住已被定义的阶段,让我们来看一下G1收集器是如何作用于年老代的。

6.初始标记阶段

年轻代垃圾收集肩负着活跃对象初始标记的任务。在日志文件中被标为GC pause (young)(inital-mark) 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第19张图片

7.并发标记阶段

如果发现空区域(“X”标示的),在重新标记阶段它们会被马上清除掉。当然,决定活性的审计信息也在此时被计算。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第20张图片

8.重新标记阶段

空的区域被清除和回收掉。所有区域的活性在此时计算。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第21张图片

9.复制/清理阶段

G1选择活性最低的区域,这些区域能够以最快的速度回收。然后这些区域会在年轻代垃圾回收过程中被回收。在日志中被指示为[GC pause (mixed)]。所以年轻代和年老代在同一时间被回收。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第22张图片

10.复制/清理阶段之后

被选择的区域已经被回收和压缩到图中显示的深蓝色区和深绿色区中。 
JVM面试要点:G1 垃圾收集器和如何做到可预测的停顿_第23张图片

年老代垃圾回收总结

总结下,我们可以列出一些关于G1收集器在年老代的上关键点。 
并发标记阶段

  • 当应用运行时,并发的计算活性信
  • 在疏散暂停期间,活性信息鉴定哪些区被最好的回收
  • 没有像CMS一样的清除操作

重新标记阶段

  • 使用比在CMS中使用的算法更快的Snapshot-at-the-Beginning(SATB)算法
  • 完全空的区域会被回收掉

复制/清理阶段

  • 年轻代和年老代被同时回收
  • 年老代区域基于它们的活性被选择

命令行选项最佳实践


命令行选项最佳实践

在这部分我们看一下G1收集器的多样的命令行选项。

基本命令行

为了启用G1收集器,使用:-XX:+UseG1GC 
这个是启动在已下载的JDK演示和示例里的Java2Demo程序的示例命令行: 
java -Xmx50m -Xms50m -XX:UserG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar

关键命令行开关

-XX:+UseG1GC - 告诉Java虚拟机使用G1垃圾收集器 
-XX:MaxGCPauseMillis=200 - 为最大GC暂停时间设置一个指标。这是一个软目标,Java虚拟机将尽最大努力实现它。因此,暂停时间目标有时候可能不会达到。默认值是200毫秒。 
-XX:InitiatingHeapOccupancyPercent=45 - 触发并发垃圾收集周期的整个堆的百分比时机。

最佳实践

使用G1收集器时你应该遵守的一些最佳实践 
不要设置年轻代大小 
通过-Xmn明确地设置年轻代大小来插手G1收集器的默认行为。

  • 收集时G1收集器将不再遵照暂停时间指标。所以本质上,设置年轻代大小将不会启用暂停时间目标。
  • G1收集器将不能按需扩张、收缩年轻代空间。自从大小被固定之后,大小将不再会被改变。

响应时间指标

代替使用平均响应时间(ART)做为指标,来设置XX:MaxGCPauseMillis=,考虑设置值将会符合这个时间的90%或者更高比例。这意味着90%的用户发出一个请求将不会经历高于这个目标的时间。记住,暂停时间只是一个目标,不保证总是能够达到。

什么是疏散失败?

当Java虚拟机在Survivor和晋升的对象垃圾回收期间,堆空间用光了就会发生晋升失败。堆空间不能再扩展了因为已经在最大值了,使用-XX:+PrintGCDetails参数时,这种情况会在GC日志中通过to-space-overflow指示出来。这个代价非常大。

  • 垃圾收集仍然会继续运行,空间必须被释放。
  • 没有成功复制的对象必须就地被提升。
  • 在CSet里的任何到区域的RSets的更新都会重新生成
  • 所有这些步骤代价都非常大

如何避免疏散失败

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

  • 增大-XX:G1ReservePercent=n参数值,默认是10
  • G1收集器创建一个假的上限通过尝试保留储备内存的自由假如’to-space’被渴望得到。 
    提前启动标记周期 
    使用-XX:ConcGCThreads=n选项增大标记线程的数量

G1垃圾收集器开关完整列表

这是一个G1垃圾收集器开关的完整列表,记着去使用上述的最佳实践。

选项和默认值 描述
-XX:+UseG1GC 使用垃圾优先(G1,Garbage First)收集器
-XX:MaxGCPauseMillis=n 设置垃圾收集暂停时间最大值指标。这是一个软目标,Java虚拟机将尽最大努力实现它
-XX:InitiatingHeapOccupancyPercent=n 触发并发垃圾收集周期的整个堆空间的占用比例。它被垃圾收集使用,用来触发并发垃圾收集周期,基于整个堆的占用情况,不只是一个代上(比如:G1)。0值 表示’do constant GC cycles’。默认是45
-XX:NewRatio=n 年轻代与年老代的大小比例,默认值是2
-XX:SurvivorRatio=n eden与survivor空间的大小比例,默认值8
-XX:MaxTenuringThreshold=n 最大晋升阈值,默认值15
-XX:ParallerGCThreads=n 设置垃圾收集器并行阶段的线程数量。默认值根据Java虚拟机运行的平台有所变化
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量,默认值根据Java虚拟机运行的平台有所变化
-XX:G1ReservePercent=n 为了降低晋升失败机率设置一个假的堆的储备空间的上限大小,默认值是10
-XX:G1HeapRegionSize=n 使用G1收集器,Java堆被细分成一致大小的区域。这设置个体的细分的大小。这个参数的默认值由工学意义上的基于堆的大小决定

G1收集器的垃圾收集日志


G1收集器的垃圾收集日志

我们需要涵盖的最后的主题是使用G1垃圾回收器的日志记录信息来分析性能。这部分提供你可以用来收集打印在日志里的数据和信息的开关的快速的概览。

设置日志详情

你可以设置三种不同级别的详情。 
(1)-verbosegc (和-XX:+PrintGC等效)参数设置fine日志详情级别 
sample Output 
[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扫描、对象复制、终止(附带尝试次数)。 
* 也显示’other’时间,比如花费在选择CSet上的时间、引用处理、引用排队和释放CSet。 
* 显示Eden、Survivor和总堆空间占用。 
Sample Output 
[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但是包括每个工作者线程的信息。 
Sample Output 
[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 - 显示自从Java虚拟机启动之后流逝的时间。 
Sample Output 
1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]

(2)-XX:+PrintGCDateStamps - 为每一项添加日期时间的前缀。 
Sample Output 
2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]

理解G1日志

为了理解这个日志,这部分使用实际的垃圾收集输出日志来明确一些术语。下面的示例列出了输出日志中的术语和值,你会在日志中找到它们。 
注意:更多信息请查看Poonam Bajaj’s Blog post on G1 GC logs

G1日志术语索引

Worker Start 
Parallel Time 
External Root Scanning 
Update Remembered Set 
Scanning Remembered Sets 
Object Copy 
Termination Time 
GC Worker End 
GC Worker Other 
Clear CT 
Other 
CSet 
Ref Proc 
Ref Eng 
Free CSet 
此入门教程源地址上有一些介绍,但是和Poonam Bajaj’s Blog post on G1 GC logs内容几乎相同,所以详细信息请看我翻译的Poonam Bajaj’s Blog post on G1 GC logs

总结


在这个OBE里,你已经对包含在Java虚拟机里的G1垃圾收集器有了大概的认识。首先你学习了为什么堆和垃圾收集器是任何Java虚拟机的关键部件。然后你回顾了使用CMS收集器和G1收集器的垃圾收集是如何工作的。然后你学习了关于G1收集器的命令行开关和使用它们的最佳实践。最后,你学习了如何把对象和数据记录到垃圾收集日志里。

在这个教程里,你已经学到了:

  • Java虚拟机的一些组件
  • G1垃圾收集器概览
  • 回顾CMS收集器
  • 回顾G1收集器
  • 命令行开关和最佳实践
  • G1收集器的日志

参考:转 G1垃圾收集器入门

  参考:G1 垃圾收集器入门

你可能感兴趣的:(面经系列,JVM)