Java 有哪些垃圾收集器
(两者之间存在连线则代表两个GC收集器可以搭配使用)
在上图中共有十款GC收集器,它们可以根据回收时的属性分为分代和分区两种类型:
分代收集器:Serial、ParNew、Parallel Scavenge、CMS、Serial Old(MSC)、Parallel Old
分区收集器:G1、ZGC、Shenandoah
其中Epsilon是个例外,这款收集器是JDK11提供的,这款GC收集器俗称为“废物收集器”,装载该收集器的Java程序,在运行期间不会发生任何GC相关的操作,程序所分配的堆空间一旦用完,Java程序就会因OOM原因退出。Epsilon收集器主要是用于程序上线前做测试使用,如:性能测试、内存压力测试、VM接口测试等。在程序启动时选择装载Epsilon收集器,这样可以帮助我们过滤掉GC机制引起的性能假象
分代收集器
新生代收集器
-
Serial 收集器
- 最原始的收集器,基于单线程工作
- 在发生垃圾收集时会产生SWT
- 采用复制算法
- 在单核情况下,它的效果很好
-
ParNew 收集器
- 可以称之为Serial 的多线程版本
- 一定程度上降低了 STW 的时间
- 除开多线程之外,其他均与Serial 相同
-
Parallel Scavenge 收集器
-
大体上的特征与ParNew 非常像
-
唯一的不同就是它们的侧重点,ParNew 是侧重于减少 STW 的时间
-
但是 PS ,它是更加专注于程序运行的吞吐量(也就是提高/保持CPU的利用率)
-
这里比较难区分,我引用知乎老哥的结论:
Parallel Scavenge 尽量减少GC次数,每次GC时间长一些,但吞吐量较高。
ParNew进行频繁的GC,每次GC时间短一些,停顿时间短,但吞吐量相对偏低
老年代收集器
-
Serial Old 收集器(MSC)
- 工作方式同Serial ,是一款单线程的收集器
- 但是它使用的是 标记-整理算法
-
Paraller Old 收集器
- 是 PS 的老年代版本,同样采用多线程,注重吞吐量
- 但是在回收算法使用的是 标记-整理算法
-
CMS 收集器
-
全称为 ConcurrentMarkSweep
-
首款使用了并发收集的回收器(就是不停止程序,GC 线程和用户线程一起工作)
-
它追求最短回收时间,使用的是标记-清除算法
-
回收过程:
-
初始标记(STW) :主要是标记 GC Root 开始的下级(注:仅下一级)对象,这个过程会 STW,但是跟 GC Root 直接关联的下级对象不会很多,因此这个过程其实很快。
-
并发标记:根据上一步的结果,继续向下标识所有关联的对象,直到这条链上的最尽头。该阶段的GC线程会与用户线程同时执行
-
重新标记(STW):顾名思义,就是要再标记一次。因为第 2 步并没有阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾,该阶段需要在STW中执行,并且该阶段的停顿时间会比初始阶段要长不少。
-
并发清除:清除阶段是清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象(标记清除算法),所以这个阶段也是可以与用户线程同时并发进行的
-
-
总结
| 垃圾回收器类型 | 回收算法 | 线程工作方式 | 作用区域 |
|---|---|---|---|
| Serial | 复制算法 | 单线程 | 年轻代 |
| ParNew | 复制算法 | 多线程 | 年轻代 |
| Parallel Scavenge | 复制算法 | 多线程 | 年轻代 |
| MSC | 标记-整理算法 | 单线程 | 老年代 |
| Parallel Old | 标记-整理算法 | 多线程 | 老年代 |
| CMS | 标记-清除算法 | 多线程 | 老年代 |
从关注度来看,又可分为吞吐量优先、响应时间优先两大类。 一般而言,如果你的程序是更为关注用户体验度,那么可以采用响应速度优先的收集器工作,因为该类收集器造成的程序暂停不会很久。 但如若你的程序不需要与用户有特别多的交互,如批量处理、订单处理、报表计算、科学计算等类型的后台系统,那你则可以采用吞吐量优先的收集器,因为高吞吐量可以高效率地利用CPU资源
JDK8 默认的组合是 Parallel Scavenge + Parallel Old,但是JDK9默认是使用下面的G1收集器
分区收集器
G1收集器
G1(Garbage First)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器。
空间结构:
G1将Java堆划分为多个大小相等的独立的Region区域,G1收集器虽然在逻辑上存在分代的概念,但不再是物理隔阂了,也就是指在物理内存上是不分代的,内存空间会被划分为一个个的Region区,这样做的好处在于:JVM不需要再为堆空间分配连续的内存,堆空间可以是不连续物理内存来组成Region的集合
Humongous区存在的意义:可以避免一些“短命”的巨型对象直接进入年老代,节约年老代的内存空间,可以有效避免年老代因空间不足时的GC开销
G1设计初衷就是替换 CMS,成为一种全功能收集器。G1 在JDK9 之后成为服务端模式下的默认垃圾回收器,取代了 Parallel Scavenge 加 Parallel Old 的默认组合,而 CMS 被声明为不推荐使用的垃圾回收器。G1从整体来看是基于 标记-整理 算法实现的回收器,但从局部(两个Region之间)上看又是基于 标记-复制 算法实现的
回收过程:
- 初始标记:先触发STW,然后使用单条GC线程快速标记GCRoots直连的对象。
- 并发标记:与CMS的并发标记过程一致,采用多条GC线程与用户线程共同执行,根据Root根节点标记所有对象。
- 最终标记:同CMS的重新标记阶段,主要是为了纠正并发标记阶段因用户操作导致的错标、误标、漏标对象。
- 筛选回收:先对各个Region区的回收价值和成本进行排序,找出回收价值最大的Region优先回收
G1收集器正是由于「筛选回收」阶段的存在,所以才得以冠名「垃圾优先收集器」。在该阶段中,对各个Region区排序后,G1会根据用户指定的期望停顿时间(即-XX:MaxGCPauseMillis参数设定的值)选择「价值最大且最符合用户预期」的Region区进行回收