前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >做数据开发就不需要了解G1了么?

做数据开发就不需要了解G1了么?

作者头像
王知无-import_bigdata
发布2019-12-19 15:20:03
8650
发布2019-12-19 15:20:03
举报
最近在看关于G1垃圾收集的文章,看了很多国内与国外的资料,本文对G1的这些资料进行了整理。这篇合适JVM垃圾回收有一定基础的同学,作为G1入门可以看一下。

为什么要学G1

G1(Garbadge First Collector)作为一款JVM最新的垃圾收集器,可以解决CMS中Concurrent Mode Failed问题,尽量缩短处理超大堆的停顿,在G1进行垃圾回收的时候完成内存压缩,降低内存碎片的生成。G1在堆内存比较大的时候表现出比较高吞吐量和短暂的停顿时间,而且已成为Java 9的默认收集器。未来替代CMS只是时间的问题。

G1的GC原理

Region

G1的内存结构和传统的内存空间划分有比较的不同。G1将内存划分成了多个大小相等的Region(默认是512K),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示Eden、Survivor、Old、Humongous。其中E、S属于年轻代,O与H属于老年代。 示意图如下:

H表示Humongous。从字面上就可以理解表示大的对象(下面简称H对象)。当分配的对象大于等于Region大小的一半的时候就会被认为是巨型对象。H对象默认分配在老年代,可以防止GC的时候大对象的内存拷贝。通过如果发现堆内存容不下H对象的时候,会触发一次GC操作。

跨代引用

在进行Young GC的时候,Young区的对象可能还存在Old区的引用, 这就是跨代引用的问题。为了解决Young GC的时候,扫描整个老年代,G1引入了Card TableRemember Set的概念,基本思想就是用空间换时间。这两个数据结构是专门用来处理Old区到Young区的引用。Young区到Old区的引用则不需要单独处理,因为Young区中的对象本身变化比较大,没必要浪费空间去记录下来。

  • RSet:全称Remembered Sets, 用来记录外部指向本Region的所有引用,每个Region维护一个RSet。
  • Card: JVM将内存划分成了固定大小的Card。这里可以类比物理内存上page的概念。

下图展示的是RSetCard的关系。每个Region被分成了多个Card,其中绿色部分的Card表示该Card中有对象引用了其他Card中的对象,这种引用关系用蓝色实线表示。RSet其实是一个HashTable,Key是Region的起始地址,Value是Card Table (字节数组),字节数组下标表示Card的空间地址,当该地址空间被引用的时候会被标记为dirty_card

SATB

SATB的全称(Snapshot At The Beginning)字面意思是开始GC前存活对象的一个快照。SATB的作用是保证在并发标记阶段的正确性。如何理解这句话? 首先要介绍三色标记算法。

  • 黑色:根对象,或者该对象与它的子对象都被扫描
  • 灰色:对象本身被扫描,但还没扫描完该对象中的子对象
  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象。

在GC扫描C之前的颜色如下:

在并发标记阶段,应用线程改变了这种引用关系:

代码语言:javascript
复制
A.c=C
B.c=null

得到如下结果。

在重新标记阶段扫描结果如下:

这种情况下C会被当做垃圾进行回收。Snapshot的存活对象原来是A、B、C,现在变成A、B了,Snapshot的完整遭到破坏了,显然这个做法是不合理。 G1采用的是pre-write barrier解决这个问题。简单说就是在并发标记阶段,当引用关系发生变化的时候,通过pre-write barrier函数会把这种这种变化记录并保存在一个队列里,在JVM源码中这个队列叫satb_mark_queue。在remark阶段会扫描这个队列,通过这种方式,旧的引用所指向的对象就会被标记上,其子孙也会被递归标记上,这样就不会漏标记任何对象,snapshot的完整性也就得到了保证。

SATB的方式记录活对象,也就是那一时刻对象snapshot, 但是在之后这里面的对象可能会变成垃圾, 叫做浮动垃圾(floating garbage),这种对象只能等到下一次收集回收掉。在GC过程中新分配的对象都当做是活的,其他不可达的对象就是死的。 如何知道哪些对象是GC开始之后新分配的呢? 在Region中通过top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS来记录新配的对象。示意图如下:

每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。这里引用R大的解释。

其中top是该region的当前分配指针,[bottom, top)是当前该region已用(used)的部分,[top, end)是尚未使用的可分配空间(unused)。 (1): [bottom, prevTAMS): 这部分里的对象存活信息可以通过prevBitmap来得知。 (2): [prevTAMS, nextTAMS): 这部分里的对象在第n-1轮concurrent marking是隐式存活的。 (3): [nextTAMS, top): 这部分里的对象在第n轮concurrent marking是隐式存活的。

G1的GC模式

Young GC

Young GC 回收的是所有年轻代的Region。当E区不能再分配新的对象时就会触发。E区的对象会移动到S区,当S区空间不够的时候,E区的对象会直接晋升到O区,同时S区的数据移动到新的S区,如果S区的部分对象到达一定年龄,会晋升到O区。 Yung GC过程示意图如下:

Mixed GC

Mixed GC 翻译过来叫混合回收。之所以叫混合是因为回收所有的年轻代的Region+部分老年代的Region。 1、为什么是老年代的部分Region? 2、什么时候触发Mixed GC? 这两个问题其实可以一并回答。回收部分老年代是参数-XX:MaxGCPauseMillis,用来指定一个G1收集过程目标停顿时间,默认值200ms,当然这只是一个期望值。G1的强大之处在于他有一个停顿预测模型(Pause Prediction Model),他会有选择的挑选部分Region,去尽量满足停顿时间,关于G1的这个模型是如何建立的,这里不做深究。 Mixed GC的触发也是由一些参数控制。比如XX:InitiatingHeapOccupancyPercent表示老年代占整个堆大小的百分比,默认值是45%,达到该阈值就会触发一次Mixed GC。

Mixed GC主要可以分为两个阶段: 1、全局并发标记(global concurrent marking) 全局并发标记又可以进一步细分成下面几个步骤:

  • 初始标记(initial mark,STW)。它标记了从GC Root开始直接可达的对象。初始标记阶段借用young GC的暂停,因而没有额外的、单独的暂停阶段。
  • 并发标记(Concurrent Marking)。这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息。过程中还会扫描上文中提到的SATB write barrier所记录下的引用。
  • 最终标记(Remark,STW)。标记那些在并发标记阶段发生变化的对象,将被回收。
  • 清除垃圾(Cleanup,部分STW)。这个阶段如果发现完全没有活对象的region就会将其整体回收到可分配region列表中。清除空Region。

2、拷贝存活对象(Evacuation) Evacuation阶段是全暂停的。它负责把一部分region里的活对象拷贝到空region里去(并行拷贝),然后回收原本的region的空间。Evacuation阶段可以自由选择任意多个region来独立收集构成收集集合(collection set,简称CSet),CSet集合中Region的选定依赖于上文中提到的停顿预测模型,该阶段并不evacuate所有有活对象的region,只选择收益高的少量region来evacuate,这种暂停的开销就可以(在一定范围内)可控。

Mixed GC的清理过程示意图如下:

Full GC

G1的垃圾回收过程是和应用程序并发执行的,当Mixed GC的速度赶不上应用程序申请内存的速度的时候,Mixed G1就会降级到Full GC,使用的是Serial GC。Full GC会导致长时间的STW,应该要尽量避免。 导致G1 Full GC的原因可能有两个:

  • Evacuation的时候没有足够的to-space来存放晋升的对象;
  • 并发处理过程完成之前空间耗尽

Young GC日志

通过使用-XX:+PrintGCDetails参数查看的Young GC日志如下:

① 四个关键信息

  • 2016-12-12T10:40:18.811-0500:GC发生的时间(通过设置-XX:+PrintGCDateStamps打印)
  • 29.959:相对JVM启动的时间
  • G1 Evacuation Pause (young):GC类型,表示这是evacuation停顿,并且是Young GC。
  • 0.0305171 sec:本次GC耗时。

② 所有并行任务

  • Parallel Time:26.6 。并行任务花费的STW的时间,从收集开始到最后一个GC线程结束。
  • GC Workers:4 。并行收集的线程数量。通过 -XX:ParallelGCThreads。当CPU数量小于8时,该值为CPU个数,最大设置成8,对于多于8个的CPU,将默认取CPU个数的5/8。
  • GC Worker start:最小|最大时间戳表示第一个线程和最后一个线程的启动时间。理想情况下希望同时启动。
  • **Ext Root Scanning **:扫描外部根节点的时间。外部节点包括JNI、全局变量、线程栈等。
  • **Update RS (Remembered Set or RSet) **:每个线程更新RSet的时间。
  • **Scan RS **: 扫描每个CSet中Region的RSet,避免了扫描整个老年代。
  • Code Root Scanning:扫描code root耗时。Code Root是JIT编译后的代码里引用了heap中的对象。
  • Object Copy:拷贝存活对象到新的Region.
  • Termination: 当GC线程完成任务之后尝试结束到真正结束耗时。因为在结束前他会检查其他线程是否有未完成的任务,帮助完成之后再结束。
  • GC Worker Other:线程花费在其他工作上的时间
  • GC Worker Total:每个线程花费的时间总和。
  • GC Worker End: 每个线程的结束时间。最小|最大时间戳表示第一个线程和最后一个线程的结束时间。理想情况下希望同时结束。

③ 串行任务

  • Code Root Fixup:修复GC期间code root指针改变的耗时
  • Code Root Purge:清除code root耗时
  • Clear CT:清除card tables 中的dirty card的耗时

④其他事项 其他事项共耗时3.7ms,其他事项包括选择CSet,处理已用对象,引用入ReferenceQueues,释放CSet中的region。

⑤各代变化

  • Eden: 1097.0M(1097.0M)->0.0B(967.0M):表明了Young GC被触发,因为Eden区已经满了(分配了1097M 已经使用了1097.0M),并且Eden区都被清空了(0B),下次垃圾回收Eden区大小降到967M。
  • Survivors: 13.0M->139.0M:Young GC之后,Survivor从13M增加到了139M
  • Heap: 1694.4M(2048.0M)->736.3M(2048.0M):开始前整个堆占用了1694.4M,最大可分配2048M,在收集之后,整个堆占用736M,最大可分配没有变还是2048M。

⑥ 这次回收耗时

  • user=0.08:在垃圾回收时,花费在用户代码上的CPU时间。这个时间包含了所有线程运行的CPU时间,所以比real-time大很多
  • sys=0.00: 花费在系统内核上的时间。
  • real=0.03: 垃圾回收的实际时间。这里包括了其他进程的时间和等待时间。

①标明标记阶段开始

  • GC pause (G1 Evacuation Pause) (young) (initial-mark):利用STW停顿期间,跟踪所有可达对象,该阶段和Young GC一起执行。同时该阶段也设置两个指针TAMS来标识已经存在的对象以及在并发标记阶段新生成的对象。 ② 第一个并发事件
  • GC concurrent-root-region-scan-start: 扫描初始化标记阶段Survivor区的root Region并标记出来。

③并发标记

  • GC concurrent-mark-start:该阶段和应用线程一起执行,并发线程数默认是并行线程数的四分之一。可以通过-XX:ConcGCThreads显示指定。

④ STW阶段

  • GC remark / Finalize Marking / GC ref-proc / Unloading: 这个阶段

⑤ 这也是STW阶段 -GC cleanup: 这个阶段没有存活对象的Old Region和Humongous Region将被释放和清空。为了准备下次GC,在CSets中的Old Regions会根据他们的回收收益的大小排序。为了准备下一次标记,previous bitmaps 和 next bitmaps会被交换。同时并行线程会标记那些inital mark阶段生成的对象,以及至少存在一个存活对象的region的bitmap。 ⑥这也是一个并发阶段

  • GC concurrent-cleanup-start:处理第5阶段所有空的Region。每个Region中的RSet被清空,当所有的Region都被清理完成,他们会被加入到一个临时表中,最终会被合并到master free list。

Mixed GC

当并发标记完成后,在Young GC日志后面紧随着Mixed GC,下面是Mixed GC日志。可以看到Mixed GC日志和前面介绍的Young GC很相似,只有两个不同点: 1、第一行会表示这是一个Mixed GC 2、收集的集合里包含了老年代(Old Region),由并发标记阶段确定的。

Full GC

Full GC的日志结果如下。

需要注意的是如果是几天一次Full GC,则是正常现象,但是每小时频繁GC就需要调优了。

其他

建议大家开启-XX:+PrintAdaptiveSizePolicy-XX:+PrintTenuringDistribution两个标签,可以帮助大家更好的分析日志。

  • -XX:+PrintAdaptiveSizePolicy: 显示收集器工效(Collector ergonomics)
  • -XX:+PrintTenuringDistribution: Survivor区的使用和分布

Young GC开启-XX:+PrintAdaptiveSizePolicy之后的日志如下:

① 告诉我们有多少在dirty card队列里的cards等待被处理。并且展示了预计处理时间(包括了更新RSet和扫描RSet的时间); ② 有多少Region将被加入到这次GC中 ③ 选择出CSets并且估算这次收集时间 ④ 该行并不一定在Young GC日志中出现,如果花费在GC上的时间比应用线程大到一个阈值的时候,G1可以动态扩大堆大小。如果你设置了最大堆和最小堆的大小相等,该行不会出现 ⑤ 当并发标记开始时出现。

Young GC后面是并发回收日志。

Young GC日志中还可能存在关于Mixed GC的日志:

①告诉我们Mixed GC开始,原因是可回收垃圾百分比(22.62%)大于了我们的阈值(5%)。

下面是Mixed GC开启-XX:+PrintAdaptiveSizePolicy之后执行日志

① 该阶段包括CSet和一部分Young Region的选择 ②描述Mixed GC时,Old Region被加入到CSet中。默认情况下,G1只把10%的Old Region加入到CSet中,通过配置-XX:G1OldCSetRegionThresholdPercent=X可以更改 ③提供最终的CSet和停顿预测 ④描述Mixed GC状态细节。在这个案例中,我们仍然有535个Old Region可以被回收,大约305363768字节,占整个堆大小的14.22%。由于仍然大于阈值,下个阶段回收仍然是Mixed GC。

下面是Full GC开启-XX:+PrintAdaptiveSizePolicy之后执行日志

① 没有空的Region用来分配对象,请求扩容堆 ② 扩容需要多少空间。到目前为止还没有真正执行扩容。 ③ 不会尝试扩容。因为没有可用的Region,所有要执行Full GC。 ④ 在最小堆小于最大堆时出现的日志。G1 在一次Full GC后,尝试缩小堆到70%。这个百分比可以通过-XX:InitiatingHeapOccupancyPercent(IHOP)调节,这个参数设置使用整个对的x%时,系统开始进行并行GC。注意是整个堆的百分比。 ⑤ 堆正在被缩小,已经缩小了多少容量。

-XX:+PrintTenuringDistribution: 可以查看每次回收期间,Survivor区的分布信息。可以帮助我们查看对象年龄的变化。

上图主要从三个层面展示Survivor区: -desired survivor size: 期望的Survivor大小。该值等于Survivor大小乘以TargetSurvivorRatio (默认50%)。

  • target threshold:目标阈值。表示一个对象的年龄,这个值可以通过每个年龄的所有对象大小相加直到大于desired survivor size计算出来。
  • age distribution: 年龄分布。包括了每个年龄所有对象的大小以及增量Survivor区大小
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据技术与架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要学G1
  • G1的GC原理
    • Region
      • 跨代引用
        • SATB
        • G1的GC模式
          • Young GC
            • Mixed GC
              • Full GC
              • Young GC日志
              • Mixed GC
              • Full GC
              • 其他
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档