分代回收机制及垃圾回收算法

GC 分类

在这里插入图片描述

  • 新生代回收(Minor GC/Young GC):指只是进行新生代的回收。
  • 老年代回收(Major GC/OldGC):指只是进行老年代的回收。目前只有 CMS 垃圾回收器会有这个单独的回收老年代的行为。 (Major GC定义是比较混乱,有说指是老年代,有的说是做整个堆的收集,这个需要你根据别人的场景来定,没有固定的说法)
  • 整堆回收(FullGC):收集整个 Java 堆和方法区(注意包含方法区)

垃圾回收算法

  • 复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使 用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,内存移动是必须实打实的移动(复制),所以对应的引用(直接指针)需要调整;空间利用率只有一半

Appel 式回收

具体做法是分配一块较大的 Eden 区和两块较小的 Survivor 空间,提高空间利用率和空间分配担保,当 Survivor 空间不够用时,需要 依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)

  • 标记-清除算法(Mark-Sweep)

算法分为“标记”和“清除”两个阶段:首先扫描所有对象标记出需要回收的对象,在标记完成后扫描回收所有被标记的对象,所以需要扫描两遍。

问题:回收效率略低;标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连 续内存而不得不提前触发另一次垃圾回收动作

  • 标记-整理算法(Mark-Compact)

首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端 边界以外的内存。

问题:效率偏低;没有内存碎片;标记整理与标记清除算法的区别主要在于对象的移动。对象移动不单单会加重系统负担,同时需要全程暂停用户线程才能进行,同时所有引用 对象的地方都需要更新(直接指针需要调整)

常见的垃圾回收器

在新生代中,每次垃圾回收时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成回收。 而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
在这里插入图片描述

回收器回收对象和算法回收器类型
Serial新生代,复制算法单线程(串行)
Serial Old老年代,标记整理算法单线程(串行)
Parallel Scavenge新生代,复制算法并行的多线程收集器
Parallel Old老年代,标记整理算法并行的多线程回收器
ParNew新生代,复制算法并行的多线程收集器
CMS老年代,标记清除算法并发的多线程回收器
G1跨新生代和老年代;标记整理 + 化整为零并发的多线程回收器
  • 单线程垃圾回收器

只适合几十兆到一两百兆的堆空间进行垃圾回收(可以控制停顿时间再 100ms 左右),但是对于超过这个大小的内存回收速度很慢 (-XX:+UseSerialGC参数设置)
在这里插入图片描述

  • 多线程并行垃圾回收器

适合回收堆空间上百兆~几个 G
在这里插入图片描述

  • 并发垃圾垃圾回收器

ParNew

多线程垃圾回收器,与 CMS 进行配合,对于 CMS(CMS 只回收老年代),新生代垃圾回收器只有 Serial 与 ParNew 可以选。和 Serial 基本没区别,唯一的区 别:多线程,多 CPU 的,停顿时间比 Serial 少

Concurrent Mark Sweep (CMS)
CMS

  1. 初始标记-短暂,仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快。
  2. 并发标记-和用户的应用程序同时进行,进行 GCRoots 追踪的过程,标记从 GCRoots 开始关联的所有对象开始遍历整个可达分析路径的对象。这个时间比较长,所以采用并发处理(垃圾回收器线程和用户线程同时工作)
  3. 重新标记-短暂,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
  4. 并发清除

问题:

  • CPU 敏感:CMS 对处理器资源敏感,毕竟采用了并发的收集、当处理核心数不足 4 个时,CMS 对用户的影响较大。
  • 浮动垃圾:由于CMS 并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留待下一次 GC 时再清理掉。这一部分垃圾就称为“浮动垃圾”。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。 在 1.6的版本中老年代空间使用率阈值(92%) 如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
  • 会产生空间碎片:标记 - 清除算法会导致产生不连续的空间碎片

当碎片较多时,给大对象的分配带来很大的麻烦,为了解决这个问题,CMS 提供一个 参数:-XX:+UseCMSCompactAtFullCollection,一般是开启的,如果分配不了大对象,就进行内存碎片的整理过程。 这个地方一般会使用 Serial Old ,因为 Serial Old 是一个单线程,所以如果内存空间很大、且对象较多时,CMS 发生这样情况会很卡

Garbage First(G1)

设计思想

G1 将堆内存“化整为零”,将堆内存划分成多个大小相等独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。回收器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是 新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果

Region
在这里插入图片描述
Region 可能是 Eden,也有可能是 Survivor,也有可能是 Old,另外 Region 中还有一类特殊的 Humongous 区域,专门用来存储大对象。 G1 认为只要大小超过 了一个 Region 容量一半的对象即可判定为大对象。每个 Region 的大小可以通过参数-XX:G1HeapRegionSize 设定,取值范围为 1MB~32MB,且应为 2 的 N 次 幂。而对于那些超过了整个 Region 容量的超级大对象,将会被存放在 N 个连续的 Humongous Region 之中,G1 的进行回收大多数情况下都把 Humongous Region 作为老年代的一部分来进行看待

G1

  1. 初始标记( Initial Marking) 仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。 这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际并没有额外的停顿。
    TAMS 是什么? 要达到 GC与用户线程并发运行,必须要解决回收过程中新对象的分配,所以 G1 为每一个 Region 区域设计了两个名为 TAMS(Top at Mark Start)的指针, 从 Region区域划出一部分空间用于记录并发回收过程中的新对象。这样的对象认为它们是存活的,不纳入垃圾回收范围。
  2. 并发标记( ConcurrentMarking) 从 GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫 描 完 成以 后 , 并 发 时 有 引 用 变 动 的 对 象 , 这 些 对 象 会 漏 标 , 漏标的 对象会 被 一 个 叫 做SATB(snapshot-at-the-beginning)算法来解决
  3. 最终标记( Final Marking)对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。
  4. 筛选回收( Live DataCounting and Evacuation) 负责更新 Region 的统计数据,对各个 Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动, 是必须暂停用户线程,由多条收集器线程并行完成的

并发标记

使用引用计数法可达性分析判断对象是否存活并进行标记

解决 GC 运行时程序长时间挂起的问题,那就三色标记法

三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。
在这里插入图片描述

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

问题:GC 并发情况下的漏标问题

CMS 中的解决方案
Incremental Update 算法
当一个白色对象被一个黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描

G1 中的解决方案
SATB(snapshot-at-the-beginning)
刚开始做一个快照,当 B 和 C 消失的时候要把这个引用推到 GC 的堆栈,保证 C 还能被 GC 扫描到,最重要的是要把这个引用推到 GC 的堆栈,是灰色对 象指向白色的引用,如果一旦某一个引用消失掉了,我会把它放到栈(GC 方法运行时数据也是来自栈中),我其实还是能找到它的,我下回直接扫描他 就行了,那样白色就不会漏标。

最终标记( Final Marking) 对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)

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

跨代引用

堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,就存在跨代引用,那么回收新生代的话,需要跟踪从老 年代到新生代的所有引用,所以要避免每次 YGC 时扫描整个老年代,减少开销。
在这里插入图片描述
RSet(记忆集)
记录了其他 Region 中的对象到本 Region 的引用(记录跨代引用), RSet 的价值在于使得垃圾收集器不需要扫描整个堆,找到谁引用了当前分区中的对象,只需要扫描 RSet 即可。
RSet 本身就是一个 Hash 表,如果是在 G1 的话,则是在一个 Region 区里面。

CardTable(卡表)
由于做新生代 GC 时,需要扫描整个 OLD 区,效率非常低,所以 JVM 设计了 CardTable,如果一个 OLD 区 CardTable 中有对象指向 Y 区, 就将它设为 Dirty (标志位 1), 下次扫描时,只需要扫描 CARDTABLE 上是 Dirty 的内存区域即可。

字节数组 CARDTABLE 的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。 一般来说,卡页大小 都是以 2 的 N 次幂的字节数,假设使用的卡页是 2 的 10 次幂,即 1M,内存区域的起始地址是 0x0000 的话,数组 CARD_TABLE 的第 0、1、2 号元素,分别 对应了地址范围为 0x0000~ 0x03FF、0x0400 ~ 0x07FF、0x0800~0x011FF 的卡页内存

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

安全点与安全区域

  • 安全点

用户线程暂停,GC 线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化。所以 JVM 会在字节码指令中,选一些指令, 作为“安全点”,比如方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点。

GC 时要暂停业务线程,并不是抢占式中断(立马把业务线程中断)而是主动是中断。
主动式中断是设置一个标志,这个标志是中断标志,各业务线程在运行过程中会不停的主动去轮询这个标志,一旦发现中断标志为 True,就会在自己最近 的“安全点”上主动中断挂起。

  • 安全区域

要是业务线程都不执行(业务线程处于 Sleep 或者是 Blocked 状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全区域。

安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区 城看作被扩展拉伸了的安全点。
在这里插入图片描述

低延迟回收器

  • Eplison

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

  • ZGC

有类似于 G1 的 Region,但是没有分代。标志性的设计是染色指针 ColoredPointers(这个概念了解即可),染色指针有 4TB 的内存限制,但是效率极高,它是一种将少量额外的信息存储在指针上 的技术。

  • Shenandoah

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

GC 日志详解

在这里插入图片描述
GC 常用参数
-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:GCPauseIntervalMillis GC 的间隔时间
-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、付费专栏及课程。

余额充值