
Serial 收集器
单线程的垃圾收集,它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

新生代标记复制,老年代标记整理。
ParNew 收集器
Serial 收集器的多线程并行版本。在进行垃圾收集时,同样需要暂停所有工作线程。

新生代标记复制,老年代标记整理。
只有它能与 CMS 收集器配合工作。
Parallel Scavenge 收集器
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能 地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐 量(Throughput)。
$ 吞吐量 = \frac{运行用户代码时间}{运行用户代码时间 + 运行垃圾收集器时间} $
新生代标记复制。
Serial Old 收集器
针对老年代,使用标记整理。
在服务端模式下,它也可能有两种用 途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用[1],另外一种就是作为CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。

CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器(使用标记清除算法)。
分为 4 个步骤:
- 初始标记
- 并发标记
- 重新标记
- 并发清除

初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;而重 新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(增量更新 HotSpot 垃圾收集部分细节 (kicey.site) ),这个阶段的停顿时间通常会比初始标记阶段稍长一 些,但也远比并发标记阶段的时间短;最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的 对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
CMS 默认启动的回收线程数是(处理器核心数量 +3)/ 4。
需要注意的一点是 CMS 使用了三色问题中的增量更新,增量更新只能解决对象消失的问题,并没有办法解决“浮动垃圾”(在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集 时再清理掉,这一部分垃圾就称为“浮动垃圾”)的问题。
这会引发一系列的问题,比如,CMS 收集器需要在老年代中预留一部分空间以容纳“浮动垃圾”,那么在老年代满之前就会触发回收,同样的,如果不能回收获得足够的空间,那么可能是由于浮动垃圾造成的,那么就需要一次 Serial Old 进行停止用户线程的收集。
还有另一个问题是 CMS 需要进行定期(进入 Full GC 多少次,进行一次内存的整理)的内存整理。
G 1 收集器
面向堆内存任 何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而 是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略去处理。
Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个 Region容量一半的对象即可判定为大对象。对于那些超过了整个 Region容量的超级大对象, 将会被存放在N个连续的 Humongous Region 之中,G1的大多数行为都把 Humongous Region 作为老年代 的一部分来进行看待。
虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区 域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作 为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免 在整个Java堆中进行全区域的垃圾收集。
更具体的处理思路是让 G1 收集器去跟踪各个 Region 里面的垃 圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一 个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是 200 毫秒),优先处理回收价值收益最大的那些 Region,这也就是“Garbage First”名字的由来。
将 Java 堆分成多个独立 Region 后,Region 里面存在的跨 Region 引用对象如何解决?使用记忆集避免全堆作为 GC Roots 扫描,但在 G1 收集器上记忆集的应用其实要复杂很多,它的每个 Region 都维护有自己的记忆集,这些记忆集会记录下别的Region 指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。G1的记忆集在存储结构的本质上是一 种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号(即别的 Region 中具体哪一块内存指向当前 Region)。
G1 收集器使用原始快照的方式完成并发标记。
为了兼容“浮动垃圾”,G1为每一个 Region 设 计了两个名为 TAMS(Top at Mark Start)的指针,把 Region 中的一部分空间划分出来用于并发回收过 程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。G1 收集器默认在这个地址以上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围。
G1 收集过程
- 初始标记(Initial Marking):仅仅只是标记一下 GC Roots能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行 Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际并没有额外的停顿。
- 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理 SATB 记录下的在并发时有引用变动的对象,即最终标记阶段。
- 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB (原始快照)记录。
- 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

G1 除了使用写后屏障来进行同样的(由于G1的卡表结构复杂,其实是更烦琐的)卡表维护操作外,为了实现原始快照搜索 (SATB)算法,还需要使用写前屏障来跟踪并发时的指针变化情况。