垃圾回收器及 HostSpot 的细节实现

并发标记与三色标记

三色标记

在三色标记法之前有一个算法叫 Mark-And-Sweep(标记清除)。这个算法会设置一个标志位来记录对象是否被使用。最开始所有的标记位都是 0,如果发现对象是可达的就会置为 1,一步步下去就会呈现一个类似树状的结果。等标记的步骤完成后,会将未被标记的对象统一清理,再次把所有的标记位设置成 0 方便下次清理。这个算法最大的问题是 GC 执行期间需要把整个程序完全暂停,不能异步进行 GC 操作。因为在不同阶段标记清扫法的标志位 0 和 1 有不同的含义,那么新增的对象无论标记为什么都有可能意外删除这个对象。对实时性要求高的系统来说,这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决 GC 运行时程序长时间挂起的问题,那就三色标记法。三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。三色标记法很简单。首先将对象用三种颜色表示,分别是白色、灰色和黑色。
黑色:根对象,或者该对象与它的子对象都被扫描过。
灰色:对象本身被扫描,但是还没扫描完该对象的子对象。
白色:未被扫描对象,如果扫描完所有对象之后,最终为白色的为不可达对象,既垃圾对象。

在这里插入图片描述

三色标记的问题

GC 并发情况下的漏标

关于可达性分析的扫描过程,把它看作对象图上一股以灰色为波峰的波纹从黑向白推进的过程,如果用户线程此时是冻结的,只有收集器线程在工作,那不会有任何问题。但如果用户线程与收集器是并发工作呢?收集器在对象图上标记颜色,同时用户线程在修改引用关系——即修改对象图的结构,这样可能出现两种后果。一种是把原本消亡的对象错误标记为存活,这不是好事,但其实是可以容忍的,只不过产生了一点逃过本次收集的浮动垃圾而已,下次收集清理掉就好。另一种是把原本存活的对象错误标记为已消亡,这就是非常致命的后果了,程序肯定会因此发生错误,
在这里插入图片描述

在这里插入图片描述

CMS 中的解决方案

Incremental Update 算法
当一个白色对象被一个黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描
增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。

G1 中的解决方案

SATB(snapshot-at-the-beginning)
刚开始做一个快照,当 B 和 C 消失的时候要把这个引用推到 GC 的堆栈,保证 C 还能被 GC 扫描到,最重要的是要把这个引用推到 GC 的堆栈,是灰色对象指向白色的引用,如果一旦某一个引用消失掉了,我会把它放到栈(GC 方法运行时数据也是来自栈中),我其实还是能找到它的,我下回直接扫描他就行了,那样白色就不会漏标。
对应 G1 的垃圾回收过程中的:
最终标记( Final Marking)
对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。

原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

对比

SATB 算法是关注引用的删除。(B->C 的引用)
Incremental Update 算法关注引用的增加。(A->C 的引用)
G1 如果使用 Incremental Update 算法,因为变成灰色的成员还要重新扫,重新再来一遍,效率太低了。
所以 G1 在处理并发标记的过程比 CMS 效率要高,这个主要是解决漏标的算法决定的。

在这里插入图片描述

跨代引用

堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么回收新生代的话,需要跟踪从老年代到新生代的所有引用,所以要避免每次 YGC 时扫描整个老年代,减少开销。

记忆集与卡表

记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。

这种记录全部含跨代引用对象的实现方案,无论是空间占用还是维护成本都相当高昂。而在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。那设计者在实现记忆集的时候,便可以选择更为粗犷的记录粒度来节省记忆集的存储和维护成本,下面列举了一些可供选择(当然也可以选择这个范围以外的)的记录精度:
·字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。

·对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。

·卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

其中,第三种“卡精度”所指的是用一种称为“卡表”(Card Table)的方式去实现记忆集 [1] ,这也是目前最常用的一种记忆集实现形式,一些资料中甚至直接把它和记忆集混为一谈。前面定义中提到记忆集其实是一种“抽象”的数据结构,抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的具体实现。卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。关于卡表与记忆集的关系,读者不妨按照Java语言中HashMap与Map的关系来类比理解。卡表最简单的形式可以只是一个字节数组 [2] ,而HotSpot虚拟机确实也是这样做的。以下这行代码是HotSpot默认的卡表标记逻辑 [3] :

G1里面记录了其他 Region 中的对象到本 Region 的引用,
RSet 的价值在于使得垃圾收集器不需要扫描整个堆,找到谁引用了当前分区中的对象,只需要扫描 RSet 即可。
RSet 本身就是一个 Hash 表,如果是在 G1 的话,则是在一个 Region 区里面。

总结
这里描述的是 G1 处理跨代引用的细节,其实在 CMS 中也有类似的处理方式,比如 CardTable,也需要记录一个 RSet 来记录,我们对比一下,在 G1 中是每一个 Region 都需要一个 RSet 的内存区域,导致有 G1 的 RSet 可能会占据整个堆容量的 20%乃至更多。但是 CMS 只需要一份,所以就内存占用来说,G1占用的内存需求更大,虽然 G1 的优点很多,但是我们不推荐在堆空间比较小的情况下使用 G1,尤其小于 6 个 G。

安全点与安全区域

安全点

用户线程暂停,GC 线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化。所以 JVM 会在字节码指令中,选一些指令,作为“安全点”,比如方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点。
为什么它叫安全点,是这样的,GC 时要暂停业务线程,并不是抢占式中断(立马把业务线程中断)而是主动是中断。主动式中断是设置一个标志,这个标志是中断标志,各业务线程在运行过程中会不停的主动去轮询这个标志,一旦发现中断标志为 True,就会在自己最近
的“安全点”上主动中断挂起。

安全区域

为什么需要安全区域?
要是业务线程都不执行(业务线程处于 Sleep 或者是 Blocked 状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全域。安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区城看作被扩展拉伸了的安全点。

在这里插入图片描述
当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,这段时间里 JVM 要发起 GC 就不必去管这个线程了。
当线程要离开安全区域时,它要 JVM 是否已经完成了(根节点枚举,或者其他 GC 中需要暂停用户线程的阶段)
1、如果完成了,那线程就当作没事发生过,继续执行。
2、否则它就必须一直等待, 直到收到可以离开安全区域的信号为止。

低 延迟 的垃圾回收器

垃圾回收器三项指标

传统的垃圾回收器一般情况下 内存占用、吞吐量、延时 只能同时满足两个。但是现在的发展,延迟这项的目标越来越重要。所以就有低延迟的垃圾回收器。

Eplison
这个垃圾回收器不能进行垃圾回收,是一个“不干活”的垃圾回收器,由 RedHat 退出,它还要负责堆的管理与布局、对象的分配、与解释器的协作、与编译器的协作、与监控子系统协作等职责,主要用于需要剥离垃圾收集器影响的性能测试和压力测试。

ZGC
有类似于 G1 的 Region,但是没有分代。标志性的设计是染色指针 ColoredPointers,染色指针有 4TB 的内存限制,但是效率极高,它是一种将少量额外的信息存储在指针上的技术。它可以做到几乎整个收集过程全程可并发,短暂的 STW 也只与 GC Roots 大小相关而与堆空间内存大小无关,因此考科一实现任何堆空间 STW 的时间小于十毫秒的目标。

Shenandoah
第一款非 Oracle 公司开发的垃圾回收器,有类似于 G1 的 Region,但是没有分代。也用到了染色指针 ColoredPointers。
效率没有 ZGC 高,大概几十毫秒的目标。

在这里插入图片描述

GC 常用参数

参数说明
centered 文本居中right-aligned 文本居右
:-Xmn -Xms -Xmx –Xss :-年轻代 最小堆 最大堆 栈空间:
-XX:+UseTLAB使用 TLAB:
-XX:+PrintTLAB打印 TLAB 的使用情况
-XX:TLABSize设置 TLAB 大小
-XX:+DisableExplicitGC启用用于禁用对的调用处理的选项 System.gc()
-XX:+PrintGC查看 GC 基本信息
-XX:+PrintGCDetails查看 GC 详细信息
-XX:+PrintHeapAtGC每次一次 GC 后,都打印堆信息
-XX:+PrintGCTimeStamps启用在每个 GC 上打印时间戳的功能
-XX:+PrintGCApplicationConcurrentTime打印应用程序时间(低)
-XX:+PrintGCApplicationStoppedTime打印暂停时长(低)
-XX:+PrintReferenceGC记录回收了多少种不同引用类型的引用(重要性低)
-verbose:class类加载详细过程
-XX:+PrintVMOptions可在程序运行时,打印虚拟机接受到的命令行显示参数
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial打印所有的 JVM 参数、查看所有 JVM 参数启动的初始值(必须会用)
-XX:MaxTenuringThreshold升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。

Parallel 常用参数

参数说明
-XX:SurvivorRatio设置伊甸园空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为 8
-XX:PreTenureSizeThreshold大对象到底多大,大于这个值的参数直接在老年代分配
-XX:MaxTenuringThreshold升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。
-XX:+ParallelGCThreads并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同
-XX:+UseAdaptiveSizePolicy自动选择各区大小比例

CMS 常用参数

参数说明
-XX:+UseConcMarkSweepGC启用 CMS 垃圾回收器
-XX:+ParallelGCThreads并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同
-XX:CMSInitiatingOccupancyFraction使用多少比例的老年代后开始 CMS 收集,默认是 68%(近似值),如果频繁发生 SerialOld 卡顿,应该调小,(频繁 CMS 回收)
-XX:+UseCMSCompactAtFullCollection在 FGC 时进行压缩
-XX:CMSFullGCsBeforeCompaction多少次 FGC 之后进行压缩
-XX:+CMSClassUnloadingEnabled使用并发标记扫描(CMS)垃圾收集器时,启用类卸载。默认情况下启用此选项。
-XX:CMSInitiatingPermOccupancyFraction达到什么比例时进行 Perm 回收,JDK 8 中不推荐使用此选项,不能替代。
-XX:GCTimeRatio设置 GC 时间占用程序运行时间的百分比(不推荐使用)
-XX:MaxGCPauseMillis停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代

G1 常用参数

参数说明
-XX:+UseG1GC启用 CMS 垃圾收集器
-XX:MaxGCPauseMillis设置最大 GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且 JVM 将尽最大的努力(G1 会尝试调整Young 区的块数来)来实 现它。默认情况下,没有最大暂停时间值。
-XX:GCPauseIntervalMillisGC 的间隔时间
-XX:+G1HeapRegionSize分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着 size 增加,垃圾的存活时间更长,GC 间隔更长,但每次 GC 的时间也会更长
-XX:G1NewSizePercent新生代最小比例,默认为 5%
-XX:G1MaxNewSizePercent新生代最大比例,默认为 60%
-XX:GCTimeRatioGC时间建议比例,G1 会根据这个值调整堆空间
-XX:ConcGCThreads线程数量
-XX:InitiatingHeapOccupancyPercent启动 G1 的堆空间占用比例,根据整个堆的占用而触发并发 GC 周期
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值