文章目录
一、介绍
垃圾收集器是垃圾回收算法(标记-清除算法、复制算法、标记-整理算法、火车算法)的具体实现,不同种类JVM所提供的垃圾收集器可能会有很大差别,HotSpot虚拟机中的8种垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1、ZGC
垃圾收集器是分代的:
- 年轻代:见图
- 年老代:见图
- 通杀:G1
并行(Parallel)
指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;如ParNew、Parallel Scavenge、Parallel Old;
并发(Concurrent)
指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行)用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;
二、串行收集器
2.1 Serial收集器
Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;JDK1.3.1前是HotSpot新生代收集的唯一选择;
特点:
采用复制算法,单线程收集,Stop The World 进行垃圾收集时,必须暂停所有工作线程,直到完成;
Serial/Serial Old组合收集器运行示意图如下:
应用场景:
依然是HotSpot在Client模式下默认的新生代收集器;
也有优于其他收集器的地方:
- 简单高效(与其他收集器的单线程相比);
- 对单个CPU环境,Serial收集器没有线程切换开销,可获得最高的单线程收集效率;
- 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的。
2.2 Serial Old收集器
Serial Old是 Serial收集器的老年代版本;
特点:
针对老年代;
采用"标记-整理"算法(还有压缩,Mark-Sweep-Compact);
单线程收集;
三、并行收集器
3.1 ParNew收集器
ParNew垃圾收集器是Serial收集器的多线程版本。
特点:
除了多线程外,其余的行为、特点和Serial收集器一样;
与Serial收集器一样,可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等,两个收集器共用了不少代码;
在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
ParNew/Serial Old组合收集器运行示意图如下:
3.1.1 为什么只有ParNew能与CMS收集器配合
1.CMS是HotSpot在JDK1.5第一款并发收集器,让垃圾收集线程与用户线程(基本上)同时工作;
2.CMS作为老年代收集器,但无法与JDK1.4新生代收集器Parallel Scavenge配合工作;
3.因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;
3.2 Parallel Scavenge收集器
因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)。
特点:
有一些特点与ParNew收集器相似
- 新生代收集器
- 采用复制算法
- 多线程收集
- Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput)
3.3 Parallel Old收集器
Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本;JDK1.6中才开始提供;
特点:
- 针对老年代;
- 采用"标记-整理"算法;
- 多线程收集;
Parallel Scavenge/Parallel Old收集器运行示意图如下:
应用场景:
- JDK1.6及之后用来代替老年代的Serial Old收集器;
- 特别是在Server模式,多CPU的情况下;
- 在注重吞吐量以及CPU资源敏感场景,就有Parallel Scavenge加Parallel Old收集器"给力"组合
四、CMS收集器
并发标记清理(Concurrent Mark Sweep)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low latency)垃圾收集器
4.1 执行过程
1.初始标记
2.并发标记
3.重新标记
4.并发清除
5.并发重置
上述步骤只有初始化标记和重新标记会STW(Stop The World),其余三个步骤与用户线程都是并发的,下面来看每个步骤具体的细节。
4.1.1 初始标记
标记GC ROOTS直接可达的对象并将其压入标记栈(mark-stack)。标记完之后恢复用户线程。
4.1.2 并发标记
通过初始标记的对象,标记全部最终可达对象。这个过程GC线程与用户线程同时执行,用户线程同时会创建对象,变更对象,导致对象可达性发生改变,这种条件下可能会出现活动对象的漏标的情况,单纯的并发标记操作并不能保证GC的正确性。
所以还需要额外的操作,这个操作就是write barrier。即当赋值引用时,如果赋值的对象还没有被标记,将标记该对象。
4.1.3 重新标记
在并发标记其间,用户线程不断变更对象引用,此时的GC ROOTS有可能会发生变化,于是重新从当前的GC ROOTS和指针更新的区域出发(mod-union table)再进行一次标记,所以这个过程被叫作重新标记。
重新标记只会遍历那些新增没有标记过的活动对象和其间有指针更新的活动对象,如果指针更新频繁,重新标记很有可能会遍历新生代中的大部分甚至全部对象。所以如果重新标记阶段很慢,可以启动一次YGC,来减少并发标记的工作量减少其停顿时间。
4.1.4 并发清除
重新标记结束后,应用程序继续运行,此时分出一个处理器去进行垃圾回收工作。遇到没有被标记的对象(垃圾)就清空掉相应的内存块,并加入可分配列表。遇到被标记的对象保持原来的位置不动,只是重置其标记位,用于下一次GC。(不进行压缩操作,会产生内存碎片)
4.1.5 并发重置
重新调整堆的大小,并为下一次GC做好数据结构支持。
4.2 应对内存碎片化
采用了标记-清除就永远跳不过碎片化这个坑,目前CMS的解决办法是在Full GC时进行内存压缩。FullGC会回退使用SerialGC的标记整理算法进行,这时候GC带来的暂停可能会比较长,这种情况又被称为并发模式失败(Concurrent Mode Failure)
有两个参数配置:UseCMSCompactAtFullCollection 每一次Full GC都进行压缩,CMSFullGCsBeforeCompaction 经过多少次不压缩的Full GC后,执行一次带压缩的Full GC。