垃圾收集算法是内存回收的方法论,而垃圾收集器就是内存回收的具体实现。不同分代之间是有着不同的收集器的,如果两个收集器之间存在连线,那就说明他们可以搭配使用。

那么是如何在虚拟机中指定它使用上图中搭配的垃圾收集器呢?主要如下:
| 参数 | 说明 |
|---|---|
| -XX:+UseSerialGC | 新生代和老年代都用串行收集器,,即 Serial 和 Serial Old |
| -XX:+UseParNewGC | 新生代使用 ParNew,老年代使用 Serial Old |
| -XX:+UseParallelGC | 新生代使用 ParallerGC(即Parallel Scavenge),老年代使用 Serial Old |
| -XX:+UseParallerOldGC | 新生代使用 ParallerGC(即Parallel Scavenge) ,老年代使用 Parallel Old |
| -XX:+UseConcMarkSweepGC | 表示新生代使用 ParNew,老年代的用 CMS |
| -XX:+UseG1GC | 表示使用 G1 |
了解了上述的基本搭配,这里我们可以我们自己电脑JDK上是使用的哪一种垃圾收集器,可以使用这个命令
java -XX:+PrintCommandLineFlags -version

| 收集器 | 收集对象 | 算法 | 收集器类型 | 说明 | 适用场景 |
|---|---|---|---|---|---|
| Serial | 新生代 | 复制算法 | 单线程 | 进行垃圾收集时,必须暂停所 有工作线程,直到完成; (stop the world) | 简单高效; 适合内存不大的情况 |
| ParNew | 新生代 | 复制算法 | 并行的多线程收集器 | ParNew垃圾收集器是Serial 收集器的多线程版本 | 搭配CMS垃圾回收器的首选 |
| Parallel Scavenge 吞吐量优先 | 新生代 | 复制算法 | 并行的多线程收集器 | 类似ParNew,更加关注吞吐 量,达到一个可控制的吞吐量 | 本身是Server级别多CPU机 器上的默认GC方式,主要适 合后台运算不需要太多交 互的任务 |
吞吐量 = 运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)
垃圾收集时间 = 垃圾回收频率 * 单次垃圾回收时间
| 收集器 | 收集对象 | 算法 | 收集器类型 | 说明 | 适用场景 |
|---|---|---|---|---|---|
| Serial Old | 老年代 | 标记整理算法 | 单线程 | jdk7/8默认的老生代垃圾回收器 | Client模式下虚拟机使用 |
| Parallel Old | 老年代 | 标记整理算法 | 并行的多线程收集器 | Parallel Scavenge收集器的老年代版 本,为了配合Parallel Scavenge的面 向吞吐量的特性而开发的对应组合 | 在注重吞吐量以及CPU 资源敏感的场合采用 |
| CMS | 老年代 | 标记清除算法 | 并行与并发收集器 | 尽可能的缩短垃圾收集时用户 线程停止时间;缺点在于: 1、内存碎片 2、需要更多cpu资源 3、浮动垃圾问题,需要更大的堆空间 | 重视服务的响应速度、 系统停顿时间和用户体 验的>互联网网站或者 B/S系统。互联网后端目 前cms是主流垃圾回收器 |
并行: 垃圾收集的多线程的同时进行。
并发: 垃圾收集的多线程和应用的多线程同时进行。
| 收集器 | 收集对象 | 算法 | 收集器类型 | 说明 | 适用场景 |
|---|---|---|---|---|---|
| G1 | 跨新生代和老年代 | 标记整理+化整为零 | 并行与并发收集器 | JDK1.7才正式引入,采用分区回 收的思维,基本不牺牲吞吐量的 前提下完成低停顿的内存回收; 可预测的停顿是其最大的优势 | 面向服务端应用 的垃圾回收器, 目标为取代CMS |
| ZGC | 跨新生代和老年代 | 动态地创建和销毁 Region;动态地决 定Region的大小 | 并发,只会在枚举 根节点的阶段STW (Stop The World), 因此停顿时间不会 随着堆大小或存活 对象的多少而增加 | JDK11版本包含一个全新的垃圾 收集器ZGC,它由Oracle开发, 承诺在数TB的堆上具有非常低的 暂停时间。 | 面向服务端应用 的垃圾回收器, 目标为取代G1 |
一、Serial/Serial Old
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

注意: 其中Serial Old收集器可作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
二、ParNew
ParNew收集器其实就是Serial收集器的多线程版本,处理使用多条线程进行垃圾收集之外,其余的行为几乎和Serial收集器一致。在多CPU的环境,停顿时间比Serial垃圾收集器短。

三、Parallel Scavenge/Parallel Old
Parallel Scavenge收集器和ParNew收集器的区别是,它是主要关注吞吐量的垃圾收集器,Parallel Old收集器是在Parallel Scavenge收集器的老年代版本。

高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
四、CMS
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。一般应用在互联网网站或者B/S系统的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
CMS收集器是基于“标记-清除”算法实现的,它的运行过程相对于前面的几种更为复杂,如下:
- 初始标记 短暂,仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
- 并发标记 和用户的应用程序同时进行,进行GC Roots追踪的过程,标记从 GCRoots 开始关联的所有对象开始遍历整个可达分析路径的对象。这个时间比较长,所以采用并发处理(垃圾回收器线程和用户线程同时工作)
- 重新标记 短暂,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
- 并发清除
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

当然我们的CMS垃圾收集器也是有一些缺点的,如我们上图,CMS收集器运行示意图可以看书,就是在我们CMS在并发清除阶段,用户线程还在运行着,伴随着程序的运行自然就还会新的垃圾不断产生,这一部分垃圾在出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时在处理,这一部分垃圾就成为浮动垃圾。
另外就是由于CMS垃圾收集器中,我们的用户线程也是需要运行的,所以就需要预留有足够的内存空间给用户线程去使用,因此CMS收集器不能像其他收集器那样等老年代几乎完全被填满了在进行收集。在JDK1.5的默认配置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK 1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,
这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
所以我们从一开始的图中就可以看出在CMS收集器和Serial Old之间是有一条连线的,我们在Serial垃圾收集器中也说了,Serial有一种用途就是作为CMS的后备预案的,在并发收集发生Concurrent Mode Failure时使用。

另外还有就是CMS垃圾收集器采用的是“标记-清除”算法实现的,所以CMS收集结束后会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,也不得不使用Serial Old收集器提前触发一次Full GC。
CMS收集器还提供了一个参数-XX:+UseCMSCompactAtFullCollection,可用于设置执行多少次不压缩的Full GC后,使用Serial Old收集器来进行碎片整理。
五、G1
G1 将堆内存“化整为零”,将堆内存划分成多个大小相等独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。回收器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

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

- 初始标记
仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。 这个阶段需要停顿线程,但耗时很短,而且是借用进行 Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际并没有额外的停顿。
TAMS是什么?
要达到 GC 与用户线程并发运行,必须要解决回收过程中新对象的分配,所以 G1 为每一个 Region 区域设计了两个名为 TAMS(Top at Mark Start)的指针, 从 Region 区域划出一部分空间用于记录并发回收过程中的新对象。这样的对象认为它们是存活的,不纳入垃圾回收范围。 - 并发标记
从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,并发时有引用变动的对象,这些对象会漏标,漏标的对象会被一个叫做 SATB(snapshot-at-the-beginning)算法来解决 - 最终标记
对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。 - 筛选回收
负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动, 是必须暂停用户线程,由多条收集器线程并行完成的。
六、ZGC
关键技术:
- 有色指针(Colored Pointers)
- 加载屏障(Load Barrier)
目标:
- 支持TB级堆内存(最大4T)
- 最大GC停顿10ms
- 对吞吐量影响最大不超过15%
ZGC也是像G1一样划分Region,但更加灵活,G1一开始就把堆划分成固定大小的Region,而ZGC 可以有2MB,32MB,N×2MB 三种Size Groups,动态地创建和销毁Region,动态地决定Region的大小。256k以下的对象分配在Small Page, 4M以下对象在Medium Page,以上在Large Page。所以ZGC能更好的处理大对象的分配。
ZGC通过技术手段把STW(Stop The World)的情况控制在仅有一次,只会第一次的初始标记才会发生,这样GC停顿时间不会随着堆增大而上升了,再大也是通过并发的时间去回收。
ZGC和G1一样会做内存的压缩,CMS是Mark-Sweep标记过期对象后原地回收,这样就会造成内存碎片,越来越难以找到连续的空间,直到发生Full GC才进行压缩整理。ZGC和G1会将活着的对象都移动到另一个Region,整个回收掉原来的Region。
656

被折叠的 条评论
为什么被折叠?



