前面说了jvm的垃圾回收机制,这一节就说说这些垃圾回收机制的具体实现——目前几个主流的垃圾回收器。然后一下节再说说HostSpot虚拟机的具体实现,垃圾回收这块就差不多了。
垃圾收集器之间组合关系
先看看这几款主流垃圾收集器之间的组合关系,一般新生代和老年代采用不同垃圾回收器搭配工作进行GC。如下图中有连线交叉的便是可以进行搭配工作的。
![](https://i-blog.csdnimg.cn/blog_migrate/7af85546a8b3ddf065f0142dbc2d827e.png)
Serial 收集器
Serial 收集器是最基础、历史最悠久的收集器,它在进行垃圾收集的时候会暂停所有的工作线程,直到完成垃圾收集过程。下面是Serial垃圾收集器的运行示意图:
![](https://i-blog.csdnimg.cn/blog_migrate/a92688d1c9e4680657a8623a6dea8585.png)
ParNew 收集器
ParNew 垃圾收集器实则是Serial 垃圾收集器的多线程版本,这个多线程在于ParNew垃圾收集器可以使用多条线程进行垃圾回收。
Parallel Scavenge 收集器
也是一款新生代垃圾收集器,同样的基于标记--复制算法实现的。它最大的特点是可以控制吞吐量。
那什么是吞吐量呢?
![](https://i-blog.csdnimg.cn/blog_migrate/d6c5566e109331775428738c2772b65c.png)
Serial Old 收集器
Serial Old 收集器是Serial 收集器的老年代版本。其垃圾收集器的运行原理和Serial 收集器是一样的。
Parallel Old 收集器
Parallel Old 收集器同样是Parallel Scavenge 收集器的老年代版本,支持多线程并发收集。下面就是它的运行示意图:
![](https://i-blog.csdnimg.cn/blog_migrate/0bcd4fdb8811fbccd5840c6ded4c5c8c.png)
笔记 (直接语雀copy过来了)
# 串行GC/Serial GC
串行化GC配置方式:
* -XX:+UseSerialGC 配置串行化GC
串行化GC对年轻代使用的是`标记-复制`算法,使用的垃圾收集器为`Serial`,对老年代使用的是`标记-清除-整理`算法,使用的垃圾收集器为`Serial Old`。
在执行GC过程中,由于串行化GC在年轻代和老年代所使用的垃圾收集器都是单线程的,在执行过程中会触发`STW`事件,暂停其他所有应用线程。
此种GC不能充分利用多核CPU,需要回收较高堆内存时存在极高的GC暂停时间 。
串行GC适合`响应速度优先`,适合在`单核CPU`、`堆内存使用较少`的场景下使用。
# 并行GC/Parallel GC
并行GC配置方式:
* -XX:+UseParallelGC
* -XX:+UseParallelOldGC
* -XX:+UseParallelGC -XX:+UseParallelOldGC
并行GC对年轻代使用的是`标记-复制`算法,使用的垃圾收集器为`Parallel Scavenge`,对老年代使用的是`标记-清除-整理`算法,使用的垃圾收集器为`Parallel Old`。
并行GC在触发年轻代和老年代的垃圾回收时都会触发`STW`事件,与串行GC不同的是其使用的垃圾收集器是并行的,默认使用全部CPU的核数来执行GC,可以通过-XX:ParallelGCThreads参数来指定GC线程数。
并行垃圾收集器更适合于具有多核CPU的服务器,在GC期间,可以调度更多的CPU内核清理内存,GC暂停时间更短,且在两次GC周期的间隔中,没有更多的GC线程在执行。
并行GC适合在`吞吐量优先`、`多核CPU`、`后台运算`的场景下使用,在JDK1.8中,默认的GC策略为并行GC。
# CMS GC/Mostly Concurrent Mark and Sweep
CMS GC配置方式:
* -XX:+UseConcMarkSweepGC
* -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
CMS GC年轻代使用了`ParNewGC`,其是在`SerialGC`的基础上进行的改进版本,在执行过程中是并行执行,垃圾收集器为`ParNew`,在老年代,CMS GC使用`标记-清除`算法。
CMS GC 的设计目标是避免在老年代垃圾收集时出现长时间的卡顿,主要通过两种手段来达成此目标:
1. 不对老年代进行整理,而是使用空闲列表来管理内存空间的回收。
2. 在标记-清除阶段的大部分工作和应用线程一起并发执行。也就是说,在这些阶段并没有明显的应用线程暂停。但值得注意的是,它仍然和应用线程争抢CPU 时。默认情况下,CMS 使用的并发线程数等于CPU 核心数的1/4。
CMS GC执行共分为六个阶段
1. <span style="color:red">阶段1: Initial Mark(初始标记)</span>
这个阶段伴随着STW 暂停。初始标记的目标是标记所有的根对象,包括根对象直接引用的对象,以及被年轻代中所有存活对象所引用的对象(老年代单独回收)。
2. 阶段2: Concurrent Mark(并发标记)
在此阶段,CMS GC 遍历老年代,标记所有的存活对象,从前一阶段“Initial Mark” 找到的根对象开始算起。“并发标记”阶段,就是与应用程序同时运行,不用暂停的阶段。
3. 阶段3: Concurrent Preclean(并发预清理)
此阶段同样是与应用线程并发执行的,不需要停止应用线程。因为前一阶段【并发标记】与程序并发运行,可能有一些引用关系已经发生了改变。如果在并发标记过程中引用关系发生了变化,JVM 会通过“Card(卡片)”的方式将发生了改变的区域标记为“脏”区,这就是所谓的卡片标记(Card Marking)。
4. <span style="color:red">阶段4: Final Remark(最终标记)</span>
最终标记阶段是此次GC 事件中的第二次(也是最后一次)STW停顿。本阶段的目标是完成老年代中所有存活对象的标记。因为之前的预清理阶段是并发执行的,有可能GC 线程跟不上应用程序的修改速度。所以需要一次STW 暂停来处理各种复杂的情况。通常CMS 会尝试在年轻代尽可能空的情况下执行Final Remark阶段,以免连续触发多次STW 事件。
5. 阶段5: Concurrent Sweep(并发清除)
此阶段与应用程序并发执行,不需要STW 停顿。JVM 在此阶段删除不再使用的对象,并回收他们占用的内存空间。
6. 阶段6: Concurrent Reset(并发重置)
此阶段与应用程序并发执行,重置CMS 算法相关的内部数据,为下一次GC 循环做准备。
CMS GC适用于`响应速度优先`、`业务低延迟`、`多核CPU`的业务场景。
# G1 GC
G1GC配置方式:
* -XX:+UseG1GC
G1 GC 最主要的设计目标是:将STW 停顿的时间和分布,变成可预期且可配置的。
G1 GC 是一款软实时垃圾收集器,可以为其设置某项特定的性能指标。为了达成可预期停顿时间的指标,G1 GC 有一些独特的实现。
首先,堆不再分成年轻代和老年代,而是划分为多个(通常是2048个)可以存放对象的小块堆区域(smaller heap regions)。每个小块,可能一会被定义成Eden 区,一会被指定为Survivor区或者Old 区。在逻辑上,所有的Eden 区和Survivor 区合起来就是年轻代,所有的Old 区拼在一起那就是老年代。
G1 GC共分为三个阶段:
1. 年轻代模式转移暂停(Evacuation Pause)
G1 GC 会通过前面一段时间的运行情况来不断的调整自己的回收策略和行为,以此来比较稳定地控制暂停时间。在应用程序刚启动时,G1 还没有采集到什么足够的信息,这时候就处于初始的fully-young模式。当年轻代空间用满后,应用线程会被暂停,年轻代内存块中的存活对象被拷贝到存活区。如果还
没有存活区,则任意选择一部分空闲的内存块作为存活区。拷贝使用`标记-复制`算法。
2. 并发标记(Concurrent Marking)
G1 并发标记的过程与CMS 基本上是一样的。G1 的并发标记通过Snapshot-At-The-Beginning(起始快照)的方式,在标记阶段开始时记下所有的存活对象。即使在标记的同时又有一些变成了垃圾。通过对象的存活信息,可以构建出每个小堆块的存活状态,以便回收集能高效地进行选择。
* 阶段1: Initial Mark(初始标记)
此阶段标记所有从GC 根对象直接可达的对象。
* 阶段2: Root Region Scan(Root区扫描)
此阶段标记所有从"根区域" 可达的存活对象。根区域包括:非空的区域,以及在标记过程中不得不收集的区域。
* 阶段3: Concurrent Mark(并发标记)
此阶段和CMS 的并发标记阶段非常类似:只遍历对象图,并在一个特殊的位图中标记能访问到的对象。
* 阶段4: Remark(再次标记)
和CMS 类似,这是一次STW 停顿(因为不是并发的阶段),以完成标记过程。G1 收集器会短暂地停止应用线程,停止并发更新信息的写入,处理其中的少量信息,并标记所有在并发标记开始时未被标记的存活对象。
* 阶段5: Cleanup(清理)
最后这个清理阶段为即将到来的转移阶段做准备,统计小堆块中所有存活的对象,并将小堆块进行排序,以提升GC的效率,维护并发标记的内部状态。所有不包含存活对象的小堆块在此阶段都被回收了。有一部分任务是并发的,例如空堆区的回收,还有大部分的存活率计算。此阶段也需要一个短暂的STW 暂停。
3. 转移暂停: 混合模式(Evacuation Pause (mixed))
并发标记完成之后,G1将执行一次混合收集(mixed collection),就是不只清理年轻代,还将一部分老年代区域也加入到回收集中。混合模式的转移暂停不一定紧跟并发标记阶段。有很多规则和历史数据会影响混合模式的启动时机。比如,假若在老年代中可以并发地腾出很多的小堆块,就没有必要启动
混合模式。
因此,在并发标记与混合转移暂停之间,很可能会存在多次young 模式的转移暂停。
具体添加到回收集的老年代小堆块的大小及顺序,也是基于许多规则来判定的。其中包括指定的软实时性能指标,存活性,以及在并发标记期间收集的GC 效率等数据,外加一些可配置的JVM 选项。混合收集的过程,很大程度上和前面的fully-young gc 是一样的。
G1 GC可以认为是CMS GC的升级,其适用场景与CMS GC没有特别大的区别,在`堆内存较大`、`希望GC暂停时间可控`的场景,适合使用G1 GC。