垃圾收集器
- Serial 收集器
- ParNew 收集器
- Parallel Scavenge 收集器
- CMS 收集器
- G1 收集器
| 收集器名称 |
常用代际 |
常用搭档 |
线程数 |
新生代算法 |
老年代算法 |
| Serial |
新生代 |
Serial Old,CMS |
1 |
复制 |
标记-整理 |
| ParNew |
新生代 |
Serial Old,CMS |
N |
复制 |
标记-整理 |
| Parallel Scavenge |
新生代 |
Parallel Old,Serial Old |
N |
复制 |
标记-整理 |
| Serial Old |
老生代 |
Serial,ParNew |
1 |
- |
- |
| Parallel Old |
老生代 |
Parallel Scavenge |
N |
- |
标记-整理 |
| CMS |
老生代 |
Serial,ParNew |
N |
- |
标记-整理 |
| G1 |
全部 |
- |
N |
- |
- |
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
虽然我们对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。
Serial 收集器
Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。
新生代采用复制算法,老年代采用标记-整理算法。
虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。
ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。
新生代采用复制算法,老年代采用标记-整理算法。
它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
GC收集器的并行和并发概念补充:
- 并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。适合科学计算、后台处理等弱交互场景。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个CPU上。适合Web应用。
Parallel Scavenge 收集器(JDK8 默认收集器)
Parallel Scavenge 收集器类似于 ParNew 收集器,是 Server 模式(内存大于 2G,2个 cpu)下的默认收集器,那么它有什么特别之处呢?
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。 Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用复制算法,老年代采用标记-整理算法。
Serial Old 收集器
Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
Parallel Old 收集器
Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。
CMS 收集器(主要是 old 区使用)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。 从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。
整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程(STW),并记录下直接与root相连的对象,速度很快 ;
- 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。
但是它有下面三个明显的缺点:
- 对CPU资源敏感(会和服务抢资源);
- 无法处理浮动垃圾(在java业务程序线程与垃圾收集线程并发执行过程中又产生的垃圾,这种浮动垃圾只能等到下一次gc再清理了);
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
CMS的相关参数
- -XX:+UseConcMarkSweepGC 启用cms
- -XX:ConcGCThreads:并发的GC线程数(并非STW时间,而是和服务一起执行的线程数)
- -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩(减少碎片)
- -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次(因压缩非常的消耗时间,所以不能每次FullGC都做)
- -XX:CMSInitiatingOccupancyFraction:触发FulGC条件(默认是92)
- -XX:+UseCMSInitiatingOccupancyOnly:是否动态调节
- -XX:+CMSScavengeBeforeRemark:FullGC之前先做YGC(一般这个参数是打开的)
- -XX:+CMSClassUnloadingEnabled:启用回收Perm区(jdk1.7及以前)
G1 收集器(JDK9 默认收集器)
G1(Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征,JDK 1.7 的时候 G1 已经出世,所以 G1 可用于 1.7 以及之后的 JDK。
G1 将 Java 堆划分为多个大小相等的独立区域(Region),虽保留新生代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region 的集合。 分配大对象(直接进 Humongous 区,专门存放短期巨型对象,不用直接进老年代,避免 Full GC 的大量开销)不会因为无法找到连续空间而提前触发下一次 GC。 被视为 JDK 1.7 中HotSpot虚拟机的一个重要进化特征。
它具备以下特点:
- 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
- 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
- 空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
- 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内完成垃圾收集。
G1收集器的运作大致分为以下几个步骤:
- 初始标记(initial mark,STW):在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。
- 并发标记(Concurrent Marking):G1 GC 在整个堆中查找可访问的(存活的)对象。
- 最终标记(Remark,STW):该阶段是 STW 回收,帮助完成标记周期。
- 筛选回收(Cleanup,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率。
G1 垃圾收集机制分类
YoungGC
- 新对象进入 Eden 区
- 存活对象拷贝到 Survivor 区
- 存活时间达到年龄阈值时,对象晋升到 Old 区
MixedGC
- 不是 FullGC,回收所有的 Young 和部分 Old(根据期望的 GC 停顿时间确定 old 区垃圾收集的优先顺序)
- global concurrent marking (全局并发标记)
- Initial marking phase:标记 GC Root,STW
- Root region scanning phase:标记存活 Region
- Concurrent marking phase:标记存活的对象
- Remark phase:重新标记 STW
- Cleanup phase:部分 STW
- 相关参数
- G1MixedGCLiveThresholdPercent Old区的region被回收的时候的存活对象占比
- G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数
- G1OldCSetRegionThresholdPercent 一次Mixed GC中能被选入CSet的最多old区的region数量
- 触发的时机
- InitiatingHeapOccupancyPercent:堆占有率达到这个值则触发global concurrent marking,默认45%
- G1HeapWastePercent:在global concurrent marking结束之后,可以知道区有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到了此参数,只有达到了,下次才会发生Mixed GC
如何选择垃圾收集器
- 优先调整堆的大小让服务器自己来选择
- 如果内存小于 100M,使用串行收集器
- 如果是单核,并且没有停顿时间的要求,串行或 JVM 自己选择
- 如果允许停顿时间超过1秒,选择并行或者 JVM 自己选
- 如果响应时间最重要,并且不能超过1秒,使用并发收集器
下图有连线的可以搭配使用,官方推荐使用 G1,因为性能高