G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)

2022-11-03,,,

参考来源
JVM 体系架构
堆/栈的内存分配
静态和非静态方法的内存分配
CMS 回收算法
应用场景
CMS 垃圾收集阶段划分(Collection Phases)
CMS什么时候启动
CMS缺点
G1收集算法
G1的发展
分代垃圾回收瓶颈
G1使用场景
G1特点
G1堆内存的分配
G1的进程内存占用(Footprint)
G1 收集器收集过程
G1命令行参数
记录G1的GC日志
G1性能调优

参考来源

http://blog.csdn.net/renfufei/article/details/41897113

JVM 体系架构

https://github.com/cncounter/translation/raw/master/tiemao_2014/G1/01_1_JVM_Arch_CN.png

性能优化的关键部位:

    堆(Heap)

    JIT编译器, 新版本的JVM调优中很少需要关注.

    垃圾收集器

性能基础目标: 响应速度(responsiveness) 和/或 吞吐量(throughput)

堆/栈的内存分配

  Stack(栈)是JVM的内存指令区,顺序分配,内存大小定长,速度很快;

  Heap(堆)是JVM的内存数据区,分配不定长的内存空间;

静态和非静态方法的内存分配

非静态方法有一个隐含的传入参数,该参数是JVM给它的;

这个隐含的参数就是对象实例在Stack中的地址指针。非静态方法必须获得该隐含参数,因此非静态方法在调用前,必须先new一个对象实例,获得Stack中的地址指针,否则JVM将无法将隐含参数传给非静态方法。

静态方法无此隐含参数,因此也不需要new对象;

只要class文件被ClassLoader load进入JVM的Stack,该静态方法即可被调用。当然此时静态方法是存取不到Heap 中的对象属性的。

静态属性和动态属性:

  前面提到对象实例以及动态属性都是保存在Heap 中的,而Heap 必须通过Stack中的地址指针才能够被指令(类的方法)访问到。因此可以推断出:静态属性是保存在Stack中的,而不同于动态属性保存在Heap 中。正因为都是在Stack中,而Stack中指令和数据都是定长的,因此很容易算出偏移量,也因此不管什么指令(类的方法),都可以访问到类的静态属性。也正因为静态属性被保存在Stack中,所以具有了全局属性。

  在JVM中,静态属性保存在Stack指令内存区,动态属性保存在Heap数据内存区。

方法加载过程:

当一个class文件被ClassLoader load进入JVM后,方法指令保存在Stack中,此时Heap 区没有数据。然后程序技术器开始执行指令,

如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问Heap 数据区的;

如果是非静态方法,由于隐含参数没有值,会报错。因此在非静态方法执行前,要先new对象,在Heap 中分配数据,并把Stack中的地址指针交给非静态方法,这样程序技术器依次执行指令,而指令代码此时能够访问到Heap 数据区了。

CMS 回收算法

http://blog.csdn.net/wisgood/article/details/16368551

应用场景

CMS满足对响应时间的重要性需求 大于对吞吐量的要求;

应用中存在比较多的长生命周期的对象的应用;

CMS用于年老代的回收,目标是尽量减少应用的暂停时间,减少full gc发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。

CMS 垃圾收集阶段划分(Collection Phases)

CMS收集器在老年代堆内存的回收中执行分为以下阶段:

(1). 初始标记 (Initial Mark)

(Stop the World Event,所有应用线程暂停)

从root对象开始标记存活的对象。

暂停时间一般持续时间较短。

(2) 并发标记 (Concurrent Marking)

和Java应用程序线程并发运行;

遍历老年代的对象图,标记出活着的对象。

扫描从被标记的对象开始,直到遍历完从root可达的所有对象.

(3) 再次标记(Remark)

(Stop the World Event, 所有应用线程暂停)

查找在并发标记阶段漏过的对象,这些对象是在并发收集器完成对象跟踪之后由应用线程更新的.

(4) 并发清理(Concurrent Sweep)

回收在标记阶段(marking phases)确定为不可达的对象.

垃圾对象占用的空间添加到一个空闲列表(free list),供以后的分配使用。死对象的合并可能在此时发生. 请注意,存活的对象并没有被移动.

(5) 重置(Resetting) 清理数据结构,为下一个并发收集做准备.

CMS什么时候启动

与其他老年代的垃圾回收器相比,CMS在老年代空间占满之前就应该开始。

CMS收集会在老年代的空闲时间少于某一个阈值的时候被触发(这个阈值可以是动态统计出来的,也可以是固定设置的),而实际的回收周期可能要延迟到下一次年轻代的回收。为什么要这样,前面已经有解释了。

在某些极端恶劣的情况下,对象会直接在老年代中进行分配,并且CMS回收周期开始的时候,eden区尚有非常多的对象。这个时候初始标记阶段会有多于10-100倍的时间消耗。这个通常是因为要分配非常大的对象。几兆的数组等。为了尽量避免长时间的暂停,我们需要合理的配置

-XX:CMSWaitDuration。

启动CMS设置参数:

-XX:+UseConcMarkSweepGC

配置固定的CMS启动阈值:

-XX:+UseCMSInitiatingOccupancyOnly
-XX:MCSInitiatingOccupancyFraction=70

显示调用MCS周期

-XX:+ExlicitGCInvokesConcurrent

CMS的全量GC——FullGC

如果CMS不能够在老年代清理出足够的空间,会导致异常,使得JVM临时启动Serial Old垃圾回收方式进行回收。这个会造成长时间的stop-the-world暂停。全量的GC的原因可能有两个:

CMS垃圾回收的速度跟不上了

老年代中有大量的内存碎片

当然,也有可能是,没有为JVM分配足够多的内存,从而导致OutofMemoryException。

永久代的回收

一个导致CMS需要进行全量GC的原因是永久代中的垃圾。默认情况下,CMS是不回收永久代中的垃圾的。如果在你的应用中使用了多个类加载器,或者反射机制,那么就需要对永久代进行回收。采用参数-XX:+CMSClassUnloadingEnabled会打开永久代的垃圾回收。

利用多核:

通过使用以下的选项,可以使得CMS充分利用多核:

-XX:+CMSConcurrentMTEnabled  在并发阶段,可以利用多核
-XX:+ConcGCThreads 指定线程数量
-XX:+ParallelGCThreads 指定在stop-the-world过程中,垃圾回收的线程数,默认是cpu的个数
-XX:+UseParNewGC 年轻代采用并行的垃圾回收器

CMS缺点

    CMS占用CPU资源,4个CPU以上才能更好发挥CMS优势

    CMS并发阶段,它不会导致用户线程停顿,但会因为占用了一部分线程(或CPU资源)而导致应用程序变慢,总吞吐量会降低。

    CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程最多占用不超过25%的CPU资源。但是当CPU不足4个时(譬如2个),那么CMS对用户程序的影响就可能变得很大,如果CPU负载本来就比较大的时候,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,这也很让人受不了。

    为了解决这种情况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep / i-CMS)的CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想一样,就是在并发标记和并发清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些,速度下降也就没有那么明显,但是目前版本中,i-CMS已经被声明为“deprecated”,即不再提倡用户使用。

    产生浮动垃圾

    CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC。

    原因:

    CMS并发清理阶段,同时用户线程还在运行着,伴随程序的运行自然还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理掉它们,只好留待下一次GC时再将其清理掉。

    这一部分垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即还需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。

    在默认设置下,CMS收集器在老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数以获取更好的性能。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高将会很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

    产生大量的空间碎片

    CMS是一款基于“标记-清除”算法实现的收集器,这意味着收集结束时会产生大量空间碎片。

    空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

    为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外免费附送一个碎片整理过程,内存整理的过程是无法并发的。

    空间碎片问题没有了,但停顿时间不得不变长了。虚拟机设计者们还提供了另外一个参数-XX: CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。

G1收集算法

G1的发展

上一代的垃圾收集器(串行serial, 并行parallel, 以及CMS):

都把堆内存划分为固定大小的三个部分:

年轻代(young generation), 

年老代(old generation), 

持久代(permanent generation).

内存中的每个对象都存放在这三个区域中的一个.

G1 收集器采用一种逻辑上的划分的方式来管理堆内存.

分代垃圾回收瓶颈

传统分代垃圾回收方式的问题:Full GC所带来的应用暂停

对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的。这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆的设置限制在一个相对较小范围内,但是这样有限制了应用本身的处理能力,同样也是不可接收的。

分代垃圾回收方式确实也考虑了实时性要求而提供了并发回收器,支持最大暂停时间的设置,但是受限于分代垃圾回收的内存划分模型,其效果也不是很理想。

为了达到实时性的要求,一种新垃圾回收方式G1回收算法呼之欲出,它既支持短的暂停时间,又支持大的内存空间分配。可以很好的解决传统分代方式带来的问题。

G1使用场景

主要优势:

停顿时间可控

实时性较强,大幅减少了长时间的gc

一定程度的高吞吐

推荐使用 G1 的场景(Recommended Use Cases)

G1的首要目标是为需要大量内存的系统提供一个保证GC低延迟的解决方案. 也就是说堆内存在6GB及以上,稳定和可预测的暂停时间小于0.5秒.

如果应用程序具有如下的一个或多个特征,那么将垃圾收集器从CMS或ParallelOldGC切换到G1将会大大提升性能.

Full GC 次数太频繁或者消耗时间太长

对象分配的频率或代数提升(promotion)显著变化

受够了太长的垃圾回收或内存整理时间(超过0.5~1秒)

注意: 如果正在使用CMS或ParallelOldGC,而应用程序的垃圾收集停顿时间并不长,那么继续使用现在的垃圾收集器是个好主意. 使用最新的JDK时并不要求切换到G1收集器。

主要目标

从设计目标看G1完全是为了大型应用而准备的。

支持很大的堆

高吞吐量

--支持多CPU和垃圾回收线程

--在主线程暂停的情况下,使用并行收集

--在主线程运行的情况下,使用并发收集

实时目标:可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收

当然G1要达到实时性的要求,相对传统的分代回收算法,在性能上会有一些损失。

G1特点

并行与并发:利用多CPU、多核缩短Stop-The-World停顿的时间;

分代收集:分代概念在G1中依然得以保留。

空间整合:整体基于“标记-整理”,局部(两个Region之间)基于“复制”算法;不会产生空间碎片。

可预测的停顿:这是G1相对于CMS的另外一大优势,能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

G1堆内存的分配

    堆内存被划分为多个大小相等的 heap 区,每个heap区都是逻辑上连续的一段内存(virtual memory).

    划分的区域拥有角色,角色和老一代收集器相同的角色(eden, survivor, old);

    每个角色的区域个数都不是固定的。这在内存使用上提供了更多的灵活性。

划分区域:

1. Eden区 (年轻代)

2. Survivor区(年轻代)

3. Old区

4.(巨无霸区域)
5. (未分配区域) 还有第四种类型的对象被称为巨无霸区域(Humongous regions):
这种巨无霸区是设计了用来保存比标准heap区大50%及以上的对象, 它们存储在一组连续的区中.
最后一个类型是堆内存中的未使用区(unused areas).

分区大小:

每个heap区(Region)的大小在JVM启动时就确定了. JVM 通常生成 2000 个左右的heap区, 根据堆内存的总大小,区的size范围允许为 1Mb 到 32Mb.

存活的对象从一块区域转移(复制或移动)到另一块区域。设计成 heap 区的目的是为了并行地进行垃圾回收(的同时停止/或不停止其他应用程序线程)

备注: 截止英文原文发表时,巨无霸对象的回收还没有得到优化. 因此,您应该尽量避免创建太大(大于32MB?)的对象.

额外说明:

G1执行垃圾回收的处理方式与CMS相似. G1在全局标记段(gl阶obal marking phase)并发执行, 以确定堆内存中哪些对象是存活的。标记阶段完成后,G1就可以知道哪些heap区的empty空间最大;

它会首先回收这些区,通常会得到大量的自由空间. 这也是为什么这种垃圾收集方法叫做Garbage-First(垃圾优先)的原因。顾名思义, G1将精力集中放在可能布满可收回对象的区域, 可回收对象(reclaimable objects)也就是所谓的垃圾. G1使用暂停预测模型(pause prediction model)来达到用户定义的目标暂停时间,并根据目标暂停时间来选择此次进行垃圾回收的heap区域数量.

被G1标记为适合回收的heap区将使用转移(evacuation)的方式进行垃圾回收. G1将一个或多个heap区域中的对象拷贝到其他的单个区域中,并在此过程中压缩和释放内存. 在多核CPU上转移是并行执行的(parallel on multi-processors), 这样能减少停顿时间并增加吞吐量. 因此,每次垃圾收集时, G1都会持续不断地减少碎片, 并且在用户给定的暂停时间内执行. 这比以前的方法强大了很多. CMS垃圾收集器(Concurrent Mark Sweep,并发标记清理)不进行压缩. ParallelOld 垃圾收集只对整个堆执行压缩,从而导致相当长的暂停时间。

需要强调的是, G1并不是一款实时垃圾收集器(real-time collector). 能以极高的概率在设定的目标暂停时间内完成,但不保证绝对在这个时间内完成。 基于以前收集的各种监控数据, G1会根据用户指定的目标时间来预估能回收多少个heap区. 因此,收集器有一个相当精确的heap区耗时计算模型,并根据该模型来确定在给定时间内去回收哪些heap区.

注意 G1分为两个阶段: 并发阶段(concurrent, 与应用线程一起运行, 如: 细化 refinement、标记 marking、清理 cleanup) 和 并行阶段(parallel, 多线程执行, 如: 停止所有JVM线程, stop the world). 而 FullGC(完整垃圾收集)仍然是单线程的, 但如果进行适当的调优,则应用程序应该能够避免 full GC。

G1的进程内存占用(Footprint)

如果从 ParallelOldGC 或者 CMS收集器迁移到 G1, 您可能会看到JVM进程占用更多的内存(a larger JVM process size). 这在很大程度上与 “accounting” 数据结构有关, 如 Remembered Sets 和 Collection Sets.

Remembered Sets 简称 RSets:

跟踪指向某个heap区内的对象引用. 堆内存中的每个区都有一个 RSet. RSet 使heap区能并行独立地进行垃圾集合. RSets的总体影响小于5%.

Collection Sets 简称 CSets:

收集集合, 在一次GC中将执行垃圾回收的heap区. GC时在CSet中的所有存活数据(live data)都会被转移(复制/移动). 集合中的heap区可以是 Eden, survivor, 和/或 old generation. CSets所占用的JVM内存小于1%.

G1 收集器收集过程

首先得了解上述的G1内存分配模型。(年轻代 + 老年代);

    年轻代上的GC

    存活的对象被转移(copied or moved)到一个/或多个存活区(survivor regions). 如果存活时间达到阀值,这部分对象就会被提升到老年代(promoted to old generation regions).

    此时会有一次 stop the world(STW)暂停. 会计算出 Eden大小和 survivor 大小,给下一次年轻代GC使用. 清单统计信息 (Accounting)保存了用来辅助计算size. 诸如暂停时间目标之类的东西也会纳入考虑.

    这种方法使得调整各代区域的尺寸很容易, 让其更大或更小一些以满足需要.

    总结起来,G1的年轻代收集归纳如下:

    堆一整块内存空间,被分为多个heap区(regions).
    年轻代内存由一组不连续的heap区组成. 这使得在需要时很容易进行容量调整.
    年轻代的垃圾收集,或者叫 young GCs, 会有 stop the world 事件. 在操作时所有的应用程序线程都会被暂停(stopped).
    年轻代 GC 通过多线程并行进行.
    存活的对象被拷贝到新的 survivor 区或者老年代.

    G1老年代的GC

    和 CMS 收集器相似, G1 收集器也被设计为用来对老年代的对象进行低延迟(low pause)的垃圾收集. 下表描述了G1收集器在老年代进行垃圾回收的各个阶段.

    G1 收集器在老年代堆内存中执行下面的这些阶段. 注意有些阶段也是年轻代垃圾收集的一部分:

      初始标记(Initial Mark)

      也是年轻代收集的一部分

      (Stop the World Event,所有应用线程暂停) 此时会有一次 stop the world(STW)暂停事件;

      在G1中, 这附加在(piggybacked on)一次正常的年轻代GC. 标记可能有引用指向老年代对象的survivor区(根regions).

      扫描根区域(Root Region Scanning)

      扫描初始标记的对象的可达性(扫描 survivor 区中引用到老年代的引用) 这个阶段应用程序的线程会继续运行. 在年轻代GC可能发生之前此阶段必须完成.

      并发标记(Concurrent Marking)

      在整个堆中查找活着的对象. 此阶段应用程序的线程正在运行. 此阶段可以被年轻代GC打断(interrupted).

      再次标记(Remark)

      (Stop the World Event,所有应用线程暂停) 完成堆内存中存活对象的标记. 使用一个叫做 snapshot-at-the-beginning(SATB, 起始快照)的算法, 该算法比CMS所使用的算法要快速的多.

      清理(Cleanup) (Stop the World Event,所有应用线程暂停,并发执行)

      在存活对象和完全空闲的区域上执行统计(accounting). (Stop the world) 擦写 Remembered Sets. (Stop the world)

      重置空heap区并将他们返还给空闲列表(free list). (Concurrent, 并发) (*) 拷贝(Copying) (Stop the World Eenvt,所有应用线程暂停) 产生STW事件来转移或拷贝存活的对象到新的未使用的 heap区(new unused regions). 只在年轻代发生时日志会记录为 [GC pause (young)]. 如果在年轻代和老年代一起执行 则会被日志记录为 [GC Pause (mixed)].

    G1对老年代的GC有如下几个关键点:

    并发标记清理阶段(Concurrent Marking Phase)

    活跃度信息在程序运行的时候被并行计算出来

    活跃度(liveness)信息标识出哪些区域在转移暂停期间最适合回收.

    不像CMS一样有清理阶段(sweeping phase).

    再次标记阶段(Remark Phase)

    使用的 Snapshot-at-the-Beginning (SATB, 开始快照) 算法比起 CMS所用的算法要快得多.

    完全空的区域直接被回收.

    拷贝/清理阶段(Copying/Cleanup Phase)

    年轻代与老年代同时进行回收.

    老年代的选择基于其活跃度(liveness).

G1命令行参数

下面是启动 Java2Demo示例程序的命令行示例. Java2Demo位于下载 JDK demos and samples 后解压的文件夹中:

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

关键命令行开关

-XX:+UseG1GC 让 JVM 使用 G1 垃圾收集器.

-XX:MaxGCPauseMillis=200 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽力去达成这个目标. 所以有时候这个目标并不能达成. 默认值为 200 毫秒.

-XX:InitiatingHeapOccupancyPercent=45 启动并发GC时的堆内存占用百分比. G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行GC循环)‘. 默认值为 45 (例如, 全部的 45% 或者使用了45%).

最佳G1实践建议:

在使用 G1 作为垃圾收集器时,你应该遵循下面这些最佳实践的指导.

不要设置年轻代的大小(Young Generation Size)

假若通过 -Xmn 显式地指定了年轻代的大小, 则会干扰到 G1收集器的默认行为.

G1在垃圾收集时将不再关心暂停时间指标. 所以从本质上说,设置年轻代的大小将禁用暂停时间目标.

G1在必要时也不能够增加或者缩小年轻代的空间. 因为大小是固定的,所以对更改大小无能为力.

响应时间指标(Response Time Metrics)

设置 XX:MaxGCPauseMillis=<N> 时不应该使用平均响应时间(ART, average response time) 作为指标,而应该考虑使用目标时间的90%或者更大作为响应时间指标. 也就是说90%的用户(客户端/?)请求响应时间不会超过预设的目标值. 记住,暂停时间只是一个目标,并不能保证总是得到满足.

什么是转移失败(Evacuation Failure)?

对 survivors 或 promoted objects 进行GC时如果JVM的heap区不足就会发生提升失败(promotion failure). 堆内存不能继续扩充,因为已经达到最大值了. 当使用 -XX:+PrintGCDetails 时将会在GC日志中显示 to-space overflow (to-空间溢出)。

这是很昂贵的操作!

GC仍继续所以空间必须被释放.
拷贝失败的对象必须被放到正确的位置(tenured in place).
CSet指向区域中的任何 RSets 更新都必须重新生成(regenerated).

所有这些步骤都是代价高昂的.

如何避免转移失败(Evacuation Failure)?

要避免避免转移失败, 考虑采纳下列选项.

    增加堆内存大小

    增加 -XX:G1ReservePercent=n, 其默认值是 10.

    G1创建了一个假天花板(false ceiling),在需要更大 ‘to-space’ 的情况下会尝试从保留内存获取(leave the reserve memory free).

    更早启动标记周期(marking cycle)

    通过采用 -XX:ConcGCThreads=n 选项增加标记线程(marking threads)的数量.

G1 的 GC 参数完全列表

-XX:+UseG1GC    使用 G1 (Garbage First) 垃圾收集器
-XX:MaxGCPauseMillis=n 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标.
-XX:InitiatingHeapOccupancyPercent=n 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45.
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

上面是完整的 G1 的 GC 开关参数列表. 在使用时请记住上面所述的最佳实践.

记录G1的GC日志

我们要介绍的最后一个主题是使用日志信息来分享G1收集器的性能. 本节简要介绍垃圾收集的相关参数,以及日志中打印的相关信息. 设置日志细节(Log Detail)

可以设置3种不同的日志级别.

(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. 使用此选项会显示以下信息:

每个阶段的 Average, Min, 以及 Max 时间.
根扫描(Root Scan), RSet 更新(同时处理缓冲区信息), RSet扫描(Scan), 对象拷贝(Object Copy), 终止(Termination, 包括尝试次数).
还显示 “other” 执行时间, 比如选择 CSet, 引用处理(reference processing), 引用排队(reference enqueuing) 以及释放(freeing) CSet等.
显示 Eden, Survivors 以及总的 Heap 占用信息(occupancies).

日志输出示例

[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 级别类似, 包含每个 worker 线程信息.

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

Determining Time:

有两个参数决定了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日志的内容,并加上日志中出现的术语和值的解释说明.

Note: 更多信息请参考 Poonam Bajaj的博客: 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 – 各个工作线程(workers)启动时的时间戳(Timestamp)

Note: 日志是根据 thread 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 - 扫描外部根花费的时间(如指向堆内存的系统词典(system dictionary)等部分) Update Remembered Set(更新 RSet)

[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 - 必须更新在pause之前已经完成但尚未处理的缓冲. 花费的时间取决于cards的密度。cards越多,耗费的时间就越长。 Scanning Remembered Sets(扫描 RSets)

[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 - 查找指向 Collection Set 的指针(pointers) 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 – 每个独立的线程在拷贝和转移对象时所消耗的时间. 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 - 当worker线程完成了自己那部分对象的复制和扫描,就进入终止协议(termination protocol)。它查找未完成的工作(looks for work to steal), 一旦它完成就会再进入终止协议。 终止尝试记录(Termination attempt counts)所有查找工作的尝试次数(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 – 独立的 GC worker 停止时的时间戳.

GC worker time – 每个独立的 GC 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 – 每个GC线程中不能归属到之前列出的worker阶段的其他时间. 这个值应该很低. 过去我们见过很高的值,是由于JVM的其他部分的瓶颈引起的(例如在分层[Tiered]代码缓存[Code Cache]占有率的增加)。 Clear CT

[Clear CT: 0.6 ms]

清除 RSet 扫描元数据(scanning meta-data)的 card table 消耗的时间. Other

[Other: 6.8 ms]

其他各种GC暂停的连续阶段花费的时间. CSet

[Choose CSet: 0.1 ms]

敲定要进行垃圾回收的region集合时消耗的时间. 通常很小,在必须选择 old 区时会稍微长一点点. Ref Proc

[Ref Proc: 4.4 ms]

处理 soft, weak, 等引用所花费的时间,不同于前面的GC阶段 Ref Enq

[Ref Enq: 0.1 ms]

将 soft, weak, 等引用放置到待处理列表(pending list)花费的时间. Free CSet

[Free CSet: 2.0 ms]

释放刚被垃圾收集的 heap区所消耗的时间,包括对应的remembered sets。

G1性能调优

G1性能调优实践Spark的应用: http://dataunion.org/19227.html

G1性能的分析:http://blog.csdn.net/woshiqjs/article/details/7290513

G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)的相关教程结束。

《G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8).doc》

下载本文的Word格式文档,以方便收藏与打印。