介绍

G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器,分区既可以是年轻代也可以是老年代,同一个代的分区不需要连续。并且每个代分区的数量是可以动态调整的。为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的分区,这也是G1名称的由来。不过这个算法并不适合新生代垃圾收集,因为新生代的垃圾收集算法是复制算法,但是新生代也使用了分区机制主要是因为便于代大小的调整。
    G1 GC是设计用来取代CMS的,同CMS相比G1有以下优势:
1、可预测的停顿模型
2、避免了CMS的垃圾碎片
3、超大堆的表现更出色

G1关键概念

Region

G1里面的Region的概念不同于传统的垃圾回收算法中的分区的概念。G1默认把堆内存分为1024个分区,后续垃圾收集的单位都是以Region为单位的。Region是实现G1算法的基础,每个Region的大小相等,通过-XX:G1HeapRegionSize参数可以设置Region的大小。如下图所示:

图中的E代表是Eden区,S代表Survivor,O代表Old区,H代表humongous表示巨型对象(大小大小Region空间一半的对象)。从图中可以看出各个区域逻辑上并不是连续的。并且一个Region在某一个时刻是Eden,在另一个时刻就可能属于老年代。G1在进行垃圾清理的时候就是将一个Region的对象拷贝到另外一个Region中。

SATB

SATB的全称是Snapchat-At-The_Beginning。SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图。在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象。
如何找到在GC的过程中分配的对象呢?每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象。
解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢, G1给出的解决办法是通过Write Barrier。Write Barrier就是对引用字段进行赋值做了环切。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化。

RSet

RSet全称是Remember Set,每个Region中都有一个RSet,记录的是其他Region中的对象引用本Region对象的关系(谁引用了我的对象)。G1里面还有另外一种数据结构就Collection Set(CSet),CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。

停顿预测模型

G1收集器突出表现出来的一点是通过一个停顿预测模型来根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间。通过-XX:MaxGCPauseMillis参数来设置。这一点有点类似于ParallelScavenge收集器。关于停顿时间的设置并不是越短越好。设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成Serial GC;停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间。

#G1回收的过程
G1垃圾回收分为两个阶段:
1、全局并发标记阶段(Global Concurrent marking)
2、拷贝存活对象阶段(evacuation)

全局并发标记阶段

全局并发标记阶段是基于SATB的,与CMS有些类似,但是也有不同的地方,主要的几个阶段如下:
初始标记:该阶段会STW。扫描根集合,将所有通过根集合直达的对象压入扫描栈,等待后续的处理。在G1中初始标记阶段是借助Young GC的暂停进行的,不需要额外的暂停。虽然加长了Young GC的暂停时间,但是从总体上来说还是提高的GC的效率。
并发标记:该阶段不需要STW。这个阶段不断的从扫描栈中取出对象进行扫描,将扫描到的对象的字段再压入扫描栈中,依次递归,直到扫描栈为空,也就是说trace了所有GCRoot直达的对象。同时这个阶段还会扫描SATB write barrier所记录下的引用。
最终标记:也叫Remark,这个阶段也是STW的。这个阶段会处理在并发标记阶段write barrier记录下的引用,同时进行弱引用的处理。这个阶段与CMS的最大的区别是CMS在这个阶段会扫描整个根集合,Eden也会作为根集合的一部分被扫描,因此耗时可能会很长。
清理: 该阶段会STW。清点和重置标记状态。这个阶段有点像mark-sweep中的sweep阶段,这个阶段并不会实际上去做垃圾的收集,只是去根据停顿模型来预测出CSet,等待evacuation阶段来回收。

拷贝存活对象阶段

Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。Evacuation阶段从第一阶段选出来的Region中筛选出任意多个Region作为垃圾收集的目标,这些要收集的Region叫CSet,通过RSet实现。
筛选出CSet之后,G1将并行的将这些Region里的存活对象拷贝到其他Region中,这点类似于ParalledScavenge的拷贝过程,整个过程是完全暂停的。关于停顿时间的控制,就是通过选择CSet的数量来达到控制时间长短的目标。

G1的收集模式:

YoungGC:收集年轻代里的Region
MixGC:年轻代的所有Region+全局并发标记阶段选出的收益高的Region
无论是YoungGC还是MixGC都只是并发拷贝的阶段。

分代G1模式下选择CSet有两种子模式,分别对应YoungGC和mixedGC:
YoungGC:CSet就是所有年轻代里面的Region
MixedGC:CSet是所有年轻代里的Region加上在全局并发标记阶段标记出来的收益高的Region

G1的运行过程是这样的,会在Young GC和Mix GC之间不断的切换运行,同时定期的做全局并发标记,在实在赶不上回收速度的情况下使用Full GC(Serial GC)。初始标记是搭在YoungGC上执行的,在进行全局并发标记的时候不会做Mix GC,在做Mix GC的时候也不会启动初始标记阶段。当MixGC赶不上对象产生的速度的时候就退化成Full GC,这一点是需要重点调优的地方。

G1最佳实践

在使用G1垃圾收集器的时候遵循以下实践可以少走不少弯路:

不断调优暂停时间指标

通过XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。

不要设置新生代和老年代的大小

G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小。

关注Evacuation Failure

Evacuation Failure类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集。

G1常用参数

参数/默认值 含义

-XX:+UseG1GC 使用 G1 垃圾收集器
-XX:MaxGCPauseMillis=200 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)
-XX:InitiatingHeapOccupancyPercent=45 启动并发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
2018-05-03T10:21:43.209-0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0035356 secs] //初始标记,耗时0.0035秒
[Parallel Time: 2.4 ms, GC Workers: 8] //并行8个线程,耗时2.4ms
[GC Worker Start (ms): Min: 813.1, Avg: 813.7, Max: 813.9, Diff: 0.7]
[Ext Root Scanning (ms): Min: 0.0, Avg: 1.1, Max: 1.5, Diff: 1.5, Sum: 9.1] //每个扫描root的线程耗时
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //更新RS的耗时,G1中每块区域都有一个RS与之对应,RS记录了该区域被其他区域引用的对象。回收时,就把RS作为根集的一部分,从而加快回收
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0] //Processed Buffers就是记录引用变化的缓存空间
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //扫描RS
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //根扫描耗时
[Object Copy (ms): Min: 0.0, Avg: 0.5, Max: 1.3, Diff: 1.3, Sum: 3.6] //对象拷贝
[Termination (ms): Min: 0.0, Avg: 0.2, Max: 0.2, Diff: 0.2, Sum: 1.2]
[Termination Attempts: Min: 1, Avg: 1.8, Max: 4, Diff: 3, Sum: 14]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 1.6, Avg: 1.8, Max: 2.3, Diff: 0.8, Sum: 14.1] //GC线程耗时
[GC Worker End (ms): Min: 815.4, Avg: 815.4, Max: 815.4, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms] //清空CardTable耗时,RS是依赖CardTable记录区域存活对象的
[Other: 1.1 ms]
[Choose CSet: 0.0 ms] //选取CSet
[Ref Proc: 0.9 ms] //弱引用、软引用的处理耗时
[Ref Enq: 0.0 ms] //弱引用、软引用的入队耗时
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms] //释放被回收区域的耗时(包含他们的RS)
[Eden: 5120.0K(24.0M)->0.0B(12.0M) Survivors: 0.0B->2048.0K Heap: 16.0M(50.0M)->12.4M(50.0M)]
[Times: user=0.01 sys=0.00, real=0.01 secs]
//根区域扫描
2018-05-03T10:21:43.213-0800: [GC concurrent-root-region-scan-start]
2018-05-03T10:21:43.214-0800: [GC concurrent-root-region-scan-end, 0.0012422 secs]
// 并发标记
2018-05-03T10:21:43.214-0800: [GC concurrent-mark-start]
2018-05-03T10:21:43.214-0800: [GC concurrent-mark-end, 0.0004063 secs]
//重新标记又叫最终标记
2018-05-03T10:21:43.214-0800: [GC remark 2018-05-03T10:21:43.215-0800: [Finalize Marking, 0.0003736 secs] 2018-05-03T10:21:43.215-0800: [GC ref-proc, 0.0000533 secs] 2018-05-03T10:21:43.215-0800: [Unloading, 0.0007439 secs], 0.0013442 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
//独占清理
2018-05-03T10:21:43.216-0800: [GC cleanup 13M->13M(50M), 0.0004002 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]

这是一段完整的GC日志。从整体上看,并发标记周期和混合回收的前后都有可能穿插着新生代GC。其中并发标记周期主要是回收老年代空间,当然也包含了一次新生代GC。

----------------------------------------------------------------

欢迎关注我的微信公众号:yunxi-talk,分享Java干货,进阶Java程序员必备。

G1 GC技术解析的更多相关文章

  1. Java Hotspot G1 GC的一些关键技术

    G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用,作为体验版随着JDK 6u14版本面世,在JDK 7u4版本发行时被正式推出,相 ...

  2. java垃圾回收及gc全面解析(全面覆盖cms、g1、zgc、openj9)

    一般来说,gc的停顿时间和活跃对象的堆大小成比例,视gc线程的数量,每1GB可能会停顿1-3秒,且cpu数量通常和gc呈现阿姆达尔定律(Amdahl’s Law),而非我们直观计算的线性变化.如下: ...

  3. 深入理解JVM+G1+GC.pdf (中文版带书签)

    目录 序 VII前言 IX 第1章 JVM & GC基础知识 11.1 引言 21.2 基本术语 31.2.1 Java相关术语 41.2.2 JVM/GC通用术语 241.2.3 G1涉及术 ...

  4. Understanding G1 GC Logs--转载

    原文地址:https://blogs.oracle.com/poonam/entry/understanding_g1_gc_logs Understanding G1 GC Logs By Poon ...

  5. 学习PHP爬虫--《Webbots、Spiders和Screen Scrapers:技术解析与应用实践(原书第2版)》

    <Webbots.Spiders和Screen Scrapers:技术解析与应用实践(原书第2版)> 译者序 前言 第一部分 基础概念和技术 第1章 本书主要内容3 1.1 发现互联网的真 ...

  6. 钟表维修管理系统技术解析(一) MVC架构搭建

    钟表维修管理系统技术解析(一)  MVC架构搭建 1.1新建项目 第一步:打开VS2010界面,点击左上角文件,点击新建,选择项目 1.1(图1) 第二步:点击网站Web类型,选择ASP.net MV ...

  7. 会员卡管理系统技术解析(十八)Timer定时监听

    会员卡管理系统技术解析(十八)Timer定时监听 在web应用中,有时候客户须要一些定时程序.不须要客户自己去操作.而是由应用程序自行触发(代理)运行某些操作. 这个时候监听与定时器的配合使用就基本能 ...

  8. 干货|爱奇艺CDN巡检系统技术解析

    小结: 1. 中心处理系统 /1/将定制后的巡检任务拆分,通过配置与任务分发系统.CMDB*( configuration management database)将派发到边缘拨测系统/2/处理边缘拨 ...

  9. 现代前端技术解析:Web前端技术基础

    ​ 最近几年,越来越多的人投入到前端大军中:时至至今,前端工程师的数量仍然不能满足企业的发展需求:与此同时,互联网应用场景的复杂化提高了对前端工程师能力的要求,一部分初期前端工程师并不能胜任企业的工作 ...

随机推荐

  1. linux中Cron定时任务系统命令详解

    分类:Linux VPS教程 作者:阿川 发布时间:October 13, 2011 有很多同学在购买VPS之后,需要用到计划任务.但是又对计划任务不太了解,所以.今天我们的帮助中心主要是给大家提供一 ...

  2. Error处理:Unable to execute dex: java.nio.BufferOverflowException. Check the Eclipse log for stack tra

    [2014-04-20 20:59:23 - MyDetectActivity] Dx  trouble writing output: already prepared [2014-04-20 20 ...

  3. SpringMVC源码分析--容器初始化(五)DispatcherServlet

    上一篇博客SpringMVC源码分析--容器初始化(四)FrameworkServlet我们已经了解到了SpringMVC容器的初始化,SpringMVC对容器初始化后会进行一系列的其他属性的初始化操 ...

  4. C语言--static修饰函数

    在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条. 介绍它的第一条也是最重要的一条:隐藏. 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性 ...

  5. iOS中 扫描二维码/生成二维码详解 韩俊强的博客

    最近大家总是问我有没有关于二维码的demo,为了满足大家的需求,特此研究了一番,希望能帮到大家! 每日更新关注:http://weibo.com/hanjunqiang  新浪微博 指示根视图: se ...

  6. listview下拉刷新上拉加载扩展(一)

    前两篇实现了listview简单的下拉刷新和上拉加载,功能已经达到,单体验效果稍简陋,那么在这篇文章里我们来加一点效果,已达到我们常见的listview下拉刷新时的效果: 首先,在headview的x ...

  7. Ubuntu 12.04: How to enable root login

    1. vi /etc/lightdm/lightdm.conf and add following modifications. greeter-show-manual-login=true allo ...

  8. 【嵌入式开发】 嵌入式开发工具简介 (裸板调试示例 | 交叉工具链 | Makefile | 链接器脚本 | eclipse JLink 调试环境)

    作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42239705  参考博客 : [嵌入式开发]嵌入式 开发环境 (远 ...

  9. Android studio中的一次编译报错’Error:Execution failed for task ':app:transformClassesWithDexForDebug‘,困扰了两天

    先说下背景:随着各种第三方框架的使用,studio在编译打包成apk时,在dex如果发现有相同的jar包,不能创建dalvik虚拟机.一个apk,就是一个运行在linux上的一个虚拟机. 上图就是一直 ...

  10. 打包自己的aar库

    在比较大的 Android 项目的开发中,我们经常会遇到工程.jar 包等等之间相互引用的方式.一般我们通过在 gradle 文件中配置依赖来解决,但是如果通过include的方式来引入第三方库的时候 ...