JVM中G1垃圾收集器-刘宇

作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。

一、名词解释

1.1、吞吐量

  • 吞吐量关注的是,在一个指定的时间内,最大化一个应用的工作量
  • 如下方式来衡量一个系统吞吐量的好坏:
  • 在一个小时内同一个事务(或者任务、请求)完成的次数(TPS)
  • 数据库一小时可以完成多少次查询(QPS)
  • 对于关注吞吐量的系统,卡顿是可以接受的,因为这个系统关注长时间的大量任务的执行能力,单词快速的响应并不值得考虑。

1.2、响应能力

  • 响应能力指一个程序或者系统对请求是否能够及时响应,比如:
  • 一个桌面UI能多快地响应一个事件
  • 一个网站能够多快返回一个页面请求
  • 数据库能够多快返回查询的数据
  • 对于这类对响应能力敏感的场景,长时间的停顿是无法接受的。

二、G1的简单介绍

2.1、简单说明

  • G1收集器是一个面向服务端的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。
  • 它满足短时间GC停顿的同时能达到一个较高的吞吐量。
  • 它能够在响应能力上有个比较好的体现,并且在吞吐量也有一个理想的结果。
  • JDK7以上版本适用。

2.2、G1收集器的设计目标

  • 与应用线程同时工作,几乎不需要Stop The World(与CMS类似)。
  • 整理剩余空间,不产生内存碎片(CMS只能在Full GC时,用Stop The World整理内存碎片)。
  • GC停顿更加可控,可添加参数设置停顿时间。
  • 不牺牲系统的吞吐量
  • GC不要求额外的内存空间(CMS需要预留空间存储浮动垃圾)

2.3、G1 VS CMS、Parallel

  • G1在某些方面弥补了CMS的不足,比如,CMS使用的是mark-sweep算法,自然会产生内存碎片,然而G1基于copying算法,高效的整理剩余内存,而不需要管理碎片。
  • 对比Parallel Scavenge(基于copying)、Parallel Old收集器(基于Mark-Compact-Sweep),Parallel会对整个区域做整理导致GC停顿时间较长,而G1只是特定地整理几个region,所以G1在压缩空间方面是有优势的。
  • G1并非一个实时的收集器,与Parallel Scavenge一样,对GC停顿时间的设置并不绝对生效,只是G1有较高的几率保证不超过设定的GC停顿时间。与之前的GC收集器对比,G1会根据用户设定的GC停顿时间,只能评估哪几个region需要被回收可以满足用户的设定。
  • G1提供了更多手段,以达到对GC停顿时间的可控。
  • G1的Eden、Survivor、Old区不再固定,在内存使用效率上来说更灵活
  • G1在回收内存后会马上同时做合并空闲内存的工作,而CMS默认是在STW的时候做
  • G1会在Young GC中使用,而CMS只能在Old区使用

2.4、G1的适用场景

  • 服务端多核CPU、JVM内存占用较大的应用
  • 应用在运行过程中会产生大量内存碎片、需要经常压缩空间
  • 想要更可控、可预期的GC停顿周期;防止高并发下应用的雪崩现象

三、不同垃圾收集器对堆的划分

3.1、Hotspot虚拟机主要构成

在这里插入图片描述

3.2、传统垃圾收集器堆结构

在这里插入图片描述

3.3、G1垃圾收集器堆结构

  • heap被划分为一个个相等的不连续的内存区域(regions),每个region都有一个分代的角色:eden、survivor、old
  • 对每个角色的数量并没有强制的限定,也就是说对每种分代内存的大小,可以动态变化
  • G1最大的特点就是高效的执行回收,优先去执行那些大量对象可回收的区域(regions)
  • G1使用了GC停顿可预测的模型,来满足用户设定的GC停顿时间,根据用户设定的目标时间,G1会自动地选择哪些region要清除,一次清除多少个region。
  • G1从多个region中复制存活的对象,然后集中放入一个region中,同时整理、清除内存(copying收集算法)
    在这里插入图片描述

四、G1重要概念

  • 分区(Region):G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题——G1将整个堆分成大小相同大小的分区(Region)
  • 每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。年轻代、幸存区、老年代这些概念还存在,成为逻辑上的概念,这样方便复用之前分代框架的逻辑。
  • 同代Region在物理上不需要连续,则带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象较多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先收集垃圾最多的分区。
  • 依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。
  • G1还是一种带压缩的收集器,在回收老年代分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。
  • 收集集合(CSet):一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自eden空间、survivor空间或者老年代
  • 已记忆集合(RSet):RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。实则它是一个hash table,例如,region2有一个RSet,key是别的region1的起始值,value是一个集合,里面的元素是card table的index,代表着别的region1中的一个card里面有引用指向该region2的对象
    • 老年代引用新生代
      需要记录Rset。YGC在回收新生代时,如果新生代的对象被老年代引用,那么需要标记为存活对象。即此时的根对象有两种,一个是栈空间/全局变量的引用,一个是老年代到新生代的引用。
    • 老年代引用老年代
      需要记录Rset。混合GC时,只会回收部分老年代,被回收的老年代需要正确的标记哪些对象存活。

在这里插入图片描述

  • Snapshot-At-Beginning(SATB):SATB是G1 GC在并发标记阶段使用的初始快照算法。是一种将并发标记阶段开始时对象间的引用关系,以逻辑快照的形式进行保存的手段。
  • 并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区。

更多详情请浏览Orcle官方文档:https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

五、G1的详细介绍

5.1、G1的堆内存结构

G1收集器和其他收集器不一样,它采用了不同的堆内存划分法。G1将堆内存划分为多个固定大小的region,region的大小可在JVM启动时进行设置。通常JVM会将堆划分到2000个左右的region,并且每个region的大小会在1~32M之间。

在这里插入图片描述

5.2、G1的堆划分

实际上,G1从逻辑上将堆内存划分成了Eden、Survivor、Old Generation区域,只不过这三个区域是由若干个region区域组成的。

在这里插入图片描述

  • 图片中将region按照不同的颜色划分成了不同的角色。存活的对象将被复制或者移动到另一个region中去。region的设计在于可以并行收集而不去终止用户的线程。
  • 由图中可以看出,堆被划分成了Eden、Survivor、Old Generation的region区域。实际上,还有第四种region区域,它就是Humongous,它由于存储那些大小超过region大小50%或更大的对象的。最后一种类型的区域是堆中未使用的区域。
  • Humongous区域是G1中的特殊区域。如果一个对象占用的空间达到或者超过分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1就会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC

5.3、G1中的年轻代

  • 在G1中,堆空间内划分为大约2000个region区域,他们的大小在1Mb和32Mb之间。蓝色区域用于存放老年代对象,绿色区域用于存放年轻代对象。
  • 这些region区域不被要求是连续的空间,不像旧版本的收集器。
    在这里插入图片描述

5.4、G1中年轻代的收集器

存活的对象将被抽取(即:移动或复制)到另一个或多个survivor区域,如果当中有对象的年龄超过了阈值,那么有些对象将会被晋升到老年代的region区域中。

在这里插入图片描述

  • 它是会STW(stop the world)暂停的。Eden和survivor大小将会被重新计算给下一次年轻代GC使用。整体的信息将会被保存起来来计算大小。同时它将会根据我我们设置的暂停时间的目标来进行计算。
  • 这种方式能够很容易的调整region大小,根据我们需要使它们变得更大或者更小。
    在这里插入图片描述
  • 最近晋升的对象显示深蓝色,survivor的region为绿色。

总结:

  • 堆是单个的内存空间,它被划成了多个region区域
  • 年轻代的内存空间是由多个不连续的region区域组成的。在需要的时候它将会更容易的去重新计算region的大小。
  • 年轻代GC的时候是存在STW事件的,所以用户线程将会被暂停
  • 年轻代的GC可以由多个线程并行执行
  • 存活的对象将被会拷贝到新的survivor或者old generation的region区域

5.5、G1中老年代收集器

和CMS收集器类似,G1收集器也是被设计成了一个针对老年代对象低延时的收集器,下面的表格详细描述了G1收集器在老年代中的几个阶段。

5.5.1、并发标记的周期阶段

G1收集器在老年代中具有如下几个阶段,注:有一些阶段也是年轻代收集的部分。

阶段描述
1、初始化标记(Stop the World Event)会产生STW事件。依赖于一个正常的young GC。标记survivor region区域(root region)可能引用到的老年代中的对象
2、根region搜索搜索survivor region区域(root region)引用到的老年代中的对象。因为是并发的,所以在程序运行中可能会发生变化。该阶段必须在young GC发生之前完成。
3、并发标记找出整个堆内存中存活的对象。在用户程序运行中可能会发生变化。该阶段会打断年轻代的GC。
4、重新标记(Stop the World Event)完成堆内存中存活对象的标记。使用一种被称之为初始快照的算法(SATB)会比使用CMS收集器更快。
5、清理(Stop the World Event and Concurrent)1、清点出有存活对象的region和没有存活对象的region(Empty Region),会触发STW。2、更新Rset,会触发STW。3、把Empty Region收集起来到可分配Region队列,Concurrent操作。
*拷贝(Stop the World Event)这些是会触发STW事件的,以便将存活的对象移动或拷贝到新的未被使用的region中。这可以通过在日志中记录为[GC pause(young)]的年轻代region区域来完成

5.5.2、初始化标记阶段

该阶段依赖于年轻代GC,来初始化标记那些存活的对象。在日志中被记录为GC pause (young)(inital-mark)。

在这里插入图片描述

5.5.3、并发标记阶段

如果找到空的region区域(如图中“X”所示),则会在重新标记阶段立即删除。此外,影响存活率的整体信息(Accounting information)也会被计算。
在这里插入图片描述

5.5.4、重新标记阶段

空的region区域会被删除和回收。随后会立即计算所有region区域的存活率。
在这里插入图片描述

5.5.5、拷贝/清理阶段

G1会选择最低存活率的region区域,将这些区域优先清除,因为这些区域收集速度是最快的。同时这些区域会和young GC同时进行收集。在日志中被记录为[GC pause (mixed)]。所以年轻代和老年代的垃圾收集是在同一时刻的。
在这里插入图片描述

5.5.6、在拷贝/清理阶段之后

在拷贝/清理阶段之后,所选择的region区域都被收集和压缩进了下图所示的深蓝色和深绿的region区域。
在这里插入图片描述

5.5.7、总结

  • 并发标记阶段
    • 当用户应用程序运行的时候,存活率信息会被并发的计算出来。
    • 这些存活率信息能够标识哪些区域是最佳的回收区域
    • 没有CMS中的清除阶段
  • 重新标记阶段
    • 使用初始快照(SATB)的算法会比使用CMS的算法快得多。
    • 完成空region的回收
  • 拷贝/清除阶段
    • 年轻代和老年代是同时被回收的
    • 老年代的region选择会基于他们的存活率

六、G1的垃圾收集模式、参数介绍

G1算法将堆划分成若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了

6.1、垃圾收集的模式

  • G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。G1会在两种GC之前不断切换运行。
  • 初始标记是在Young GC上执行的,在进行全局并发标记的时候不会做Mixed GC,在做Minxed GC的时候也不会启动初始标记阶段。

6.1.1、Young GC:

  • 选择所有年轻代中的Region。通过控制年轻代的Region个数,即年轻代内存大小,来控制Young GC的时间开销。
  • 当Eden充满时触发Young GC,将会对所有的新生代的region进行GC,回收之后所有之前属于Eden的区块全部变成白色,即不属于任何一个分区(Eden、Survivor、Old)

6.1.2、Mixed GC:

它的GC步骤分为两步:全局并发标记(global concurrent marking)、拷贝存活对象(evacuation)

  • 选择所有年轻代中的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代中的Region。在用户指定的开销目标范围内尽可能选择收益高的老年代中的Region。
  • Mixed GC也是采用的复制清理策略,当GC完成后,会重新释放空间。
  • Mixed GC不是Full GC,它只能回收部分老年代中的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续执行Mixed GC,就会使用serial old GC(Full GC)来收集整个GC heap。所以本质上,G1是不提供Full GC的
  • Mixed GC的时机是由一些参数控制的,另外这些参数也控制着哪些老年代Region会被选入CSet(收集集合)。参数如下:
    • G1HeapWastePercent:在Global Concurrent Marking结束之后,我们可以指定old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前。会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
    • G1MixedGCLiveThresholdPercent:old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet中。
    • G1MixedGCCountTarget:一次Global Concurrent Marking之后,最多执行Mixed GC的次数。
    • G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量。

6.2、G1其他参数

在这里插入图片描述

七、G1中的Global Concurrent Marking

Global Concurrent Marking的执行过程类似于CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。

执行步骤:

  • 初始标记(initial mark,STW):
    • 它标记了从GC Root开始直接可达的对象。
    • 它共用了Young GC的暂停,这是因为他们可以复用root scan操作,所以可以说global concurrent marking是伴随Young GC而发生的。
  • 并发标记(Concurrent Marking):
    • 这个阶段从GC Root开始对heap中的对象进行标记,标记线程与应用程序线程并发执行,并且收集各个Region的存活对象信息。
  • 重新标记(Remark,STW):
    • 标记那些在并发标记阶段发生变化的对象,将被回收。
  • 清理(Cleanup):
    • 清除控Region(没有存活对象的),加入到free list。
    • 只是回收了没有存活对象的Region,所以它不需要STW

八、三色标记算法

提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性。

整个三色标记算法就是从GC roots出发变量heap针对可达对象先标记white为gray,然后再标记gray为black;遍历完成之后所有可达对象都是black的,所有white都是可以回收的。

8.1、我们将对象分为三种类型:

  • 黑色:根对象,或者该对象与它的子对象都被扫描过(对象被标记过了,且它的所有field也被标记完了)
  • 灰色:对象本身被扫描,但是还没扫描完该对象中的子对象(它的field还没有被标记或标记完)
  • 白色:未被扫描的对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象(对象没有被标记到)

8.2、三色标记流程

  • 根对象被置为黑色,子对象被置为灰色
    在这里插入图片描述

  • 继续由灰色遍历,将已扫描了子对象的对象置为黑色
    在这里插入图片描述

  • 遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理
    在这里插入图片描述

8.3、三色标记漏标现象

如果在标记过程中,应用程序也在运行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失问题,即漏标,则被视为垃圾对象,这是非常严重的错误。

对象漏标过程:

  1. 当垃圾收集器扫描到下面情况时
    在这里插入图片描述

  2. 此时应用程序执行了以下操作:
    A.C=C,即A对象指向C对象
    B.C=null,即B对象的指向C对象的变量置为null
    在这里插入图片描述

  3. 此时,垃圾收集器再标记扫描的时候就会出现如下情况

    因为A对象之前就是黑色,所以再次标记扫描的时候就不会重新扫描A了,而之前B是灰色,所以会扫描它,此时发现B没有子对象,则变成了黑色。而此时我们发现,C是白色,而A又是黑色,即代表A自身已经扫描完成了,不会再往下扫描了,就出现了C漏标的现象

    在这里插入图片描述

九、SATB

  • 在G1中,使用SATB(Snapshot-At-The-Beginning)的方式,删除的时候记录所有的对象。
  • 它是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解为在GC开始之前对堆内存里的对象做一次快照(实则仅仅对于在初始化标记阶段进行“snapshot”),此时活的对象就认为是活的,从而形成一个对象图。
  • 新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象

9.1、三个步骤:

  • 在开始标记的时候生成一个快照图,标记存活对象
  • 在并发标记的时候所有被改变的对象入队(在write barrier里把所有旧的引用所指向的对象都变成非白的),这里就会解决上方的对象引用变更,出现漏标现象。对于black引用的新产生的对象则标记为black,解决GC过程中新建对象的问题。
  • 因为是在开始标记阶段进行的snapshot,所有可能存在浮动垃圾,将在下次被收集。

9.2、SATB如何解决GC过程中新建对象问题?

  • 每个region记录这两个top-at-mark-start(TAMS)-指针,分为是prevTAMSnextTAMS==。在TAMS以上的对象就是新分配的,因而被视为隐式marked,即被标记了
  • 通过这种方式我们就能找到在GC过程中新分配的对象了,并把它们认为是活的对象。

9.3、SATB如何解决GC过程中引用发生变更问题?

G1给出的解决方法就是通过write barrier(写屏障)。就是对引用字段进行赋值做了额外处理,就是将引用所指向的对象都变成非白的。

十、G1最佳实践

10.1、不断调优暂停时间指标

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

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

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

10.3、关注Evacuation Failure

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值