垃圾回收器

前言

垃圾回收器的并行并发是和操作系统并行并发不同的两个概,另外只要标记时用户线程没运行未被标记的对象就可以回收。

  • 并行:指多个 GC 进程同时运行,此时用户线程停止运行(STW,Stop The World)。
  • 并发:指用户线程与 GC 线程同时运行。

性能指标

垃圾回收器的性能有三个指标:内存占用、停顿时间、吞吐量。内存占用越高,吞吐量也越高,而垃圾回收的停顿时间会越长;垃圾回收的停顿时间越少,要么内存占用少,要么吞吐量少,因此只能满足两点。

Serial

单线程,回收时 STW,直到回收完毕。

image-20210926163100928

ParNew

Serial 的并发版本。

image-20210926163333415

Parallel Scavenge

JDK8 默认的新生代垃圾回收器,和 ParNew 相似,重点关注吞吐量,可以动态调节为最合适的停顿时间(-XX:MaxGCPauseMillis=<millis>)或最大吞吐量(-XX:GCTimeRatio=<ratio>)。

Serial Old

Serial 的老年代版本,一样是单线程的。

image-20210926164439552

Parallel Old

Parallel Scavenge 的老年代版本,一样是多线程的,特性相同,是 JDK8 默认的老年代垃圾回收器。

CMS

CMS(Concurrent Mark Sweep)是一个老年代回收器,基于标记清除算法。它以最短回收停顿为目标,追求尽可能短的停顿时间,主要应用于互联网网站或服务端上。

回收过程

它的回收过程包括四个步骤:

  1. 初始标记:需要 STW,标记 GC Roots 能直接关联的对象,速度很快。
  2. 并发标记:从 GC Roots 直接关联的对象开始遍历(可达性分析),时间较长,但和用户线程一起执行(用户线程新产生的对象是黑色的,三色指针),用户线程新建的对象用增量更新记录。
  3. 重新标记:需要 STW,修正上一步骤用户线程重新引用的白色对象(增量更新记录的),时间比初始标记长一点,但比并发标记低。
  4. 并发清除:清理已经死亡的对象(白色对象),因为标记清除算法不需要移动存活对象,所以可以和用户线程一起执行。这个步骤不会有并发标记阶段产生的增量更新(重新标记时会 STW),可以安全的删除死亡对象(白色对象)。

image-20210923165157588

优点

并发收集、低停顿。

缺点

  1. 并发虽然不暂停用户线程,但 GC 线程会占用系统资源,降低吞吐量。
  2. 使用标记清除算法会带来内存碎片的问题,导致无法分配大对象时触发 Full GC,整理内存(移动对象会 STW)。
  3. 并发标记时用户线程还在运行,可能有新对象需要分配内存,因此不能等堆满时才 GC,界限由 -XX:CMSInitiatingOccu-pancyFraction=<fraction> 控制(堆占用 fraction % 时触发 GC),太低会导致 GC 频率太高;太高会导致此时用户线程无法创建新对象,从而启用备案:临时启用 Serial Old (STW,单线程老年代标记整理算法)清理,这样停顿时间会更长了。

G1

G1(Garbage First)是一个全堆的垃圾回收器,局部基于复制算法,全局使用标志整理算法。它可以建立一个“停顿预测模型”(在 m 毫秒的片段内,垃圾回收消耗的时间不超过 n 毫秒),并且它不像以前的垃圾回收器在回收时把找到的可以回收的全部回收,它只追求在收集的速度比对象分配的速度快即可,从 G1 开始垃圾回收器都在朝这个方向努力。

G1 的分代是逻辑上的,它将整个堆分为 n 个大小相同的 Region,每个 Region 可以按照需要扮演 Eden、Survivor、老年代,Region 中有一块特殊的区域 Humongous,Humongous 用于存储大于半个 Region 的对象。

下图将堆分为 10 * 10 的 Region,Region 为空时就是逻辑上的新生代,新生代满时(亮的蓝色,仔细看)对这部分回收(young gc)。某些情况下,可以对老年代一起回收(mixed gc),图中的红色就是一次 mixed gc。垃圾回收的是一个压缩内存空间的过程,它将存活对象复制到空白的 Region,并根据对象年龄将对象复制到 S(Survivor) 区,对于 H(Humongous) 区有特殊处理。

Description of Figure 9-1 follows

回收过程

它的回收过程包括四个步骤:

  1. 初始标记:需要 STW,只标记 GC Roots 直接能关联到的对象,并修改 TAMS 指针的值,保证下一阶段并发时用户线程正确在 Region 分配新对象内存。
  2. 并发标记:从 GC Roots 开始进行可达性分析,和用户线程一起执行,耗时较长,产生的引用变动由原始快照(SATB)记录,同时也会处理 SATB 记录的引用变动。
  3. 最终标记:需要 STW,修正并发标记未处理的 SATB 记录,很快。
  4. 筛选回收:更新 Region 的统计数据,按照回收价值和成本对 Region 排序,按照用户期望的停顿时间选择若干 Region 组成回收集(CSet,Collection Set) 回收,然后把回收的 Region 中存活对象移动到空的 Region 里,再整个清掉原来的 Region。复制存活对象必须 STW,但复制可以多线程执行。

image-20210926162820780

优点

指定停顿时间,没有内存碎片。

缺点

  1. 内存占用:G1 采用逻辑分代,内存分为一个个 Region,每个 Region 都有一个记忆集(RSet,Remembered Set,指 G1 的记忆集,而不是抽象的记忆集),记忆集记录和其他 Region 对当前 Region 的引用,另外每个 Region 按照默认 512kb 分为 n 个表维持最低的引用粒度(和卡表的卡页作用相同)。

    另外为了应对 Rset 太大采用三种数据结构存储:

    • 稀疏表PTR(PerRegionTable):Map,key 为其他 Region,value 是一个集合,集合元素是被引用的卡 index。
    • 细粒度PTR:稀疏表某个 Region 的 value 集合数量超过阈值,变为位图,每一位对应一个表。
    • 粗粒度位图:细粒度 PTR size 超过阈值,变为分区位图,每一位对应一个 Region。
  2. 复杂的写屏障:G1 采用的原始快照(SATB)需要用写前屏障记录原始指针,并且 G1 的维护记忆集更复杂。由于占用更多的资源不能像 CMS 一样同步执行,只能放到消息队列中异步处理(每秒处理一次,最终标记阶段会全部处理完)。

参考

JVM G1GC的算法与实现 - Yano_nankai - 博客园 (cnblogs.com)

深入理解Java虚拟机(第3版) (豆瓣) (douban.com)

Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide, Release 8 (oracle.com)

详解 G1 垃圾收集器 (linkedkeeper.com)

记忆集和卡表 - MaXianZhe - 博客园 (cnblogs.com)

JVM G1 源码分析(三)- Remembered Sets_860MHz的专栏-CSDN博客

Java虚拟机07 - 垃圾收集器之G1 | CodeRap - 理想是人生的太阳

posted @ 2021-09-27 10:10  hligy  阅读(197)  评论(0编辑  收藏  举报