如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。


图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。

明确一个观点:虽然我们是在对各个收集器进行比较,但并非为了挑选一个最好的收集器出来。
因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器,所以我们选择的只是对具体应用最合适的收集器。

垃圾收集器

1. Serial收集器

单线程,stop the word

2. ParNew收集器

Serial收集器的多线程版本

3. Parallel Scavenge收集器

4. Serial Old收集器

Serial收集器的老年代版本

5. Parallel Old收集器

Parallel收集器的老年代版本

6. CMS收集器

concurrent mark sweep,是一种以获取最短回收停顿时间为目标的收集器,非常符合在注重用户体验的应用上使用。
CMS收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作

CMS收集器用标记—清除算法实现。
整个过程分为四个步骤:

  • 初始标记
    STW,耗时短,仅标记GC Roots能直接关联到的对象

  • 并发标记
    不会STW,耗时长,从初识标记的对象中遍历整个对象图

  • 重新标记
    STW,耗时短,修正并发标记期间用户线程导致的标记变动记录

  • 并发清除。
    不会STW,产生浮动垃圾。
    并发失败的话,启动Serial Old,会导致停顿时间变长。

  • 优点:

    • 并发收集、低停顿。
  • 缺点:

    • 对CPU资源敏感
    • 无法处理浮动垃圾
    • 使用“标记—清除”算法会导致收集结束时会有大量空间碎片产生。
    • 并发失败。
      由于浮动垃圾的存在,因此CMS必须预留一部分空间来装载这些新产生的垃圾。CMS不能像Serial Old收集器那样,等到老年代区填满了再来清理。在JDK5时,CMS会在老年代使用了68%的空间时激活,预留了32%的空间来装载浮动垃圾,这是一个比较偏保守的配置。如果实际引用中,老年代增长的不是太快,可以通过-XX:CMSInitiatingOccupancyFraction参数适当调高这个值。到了JDK6,触发的阈值就被提升至92%,只预留了8%的空间来装载浮动垃圾。
      如果CMS预留的内存无法容纳浮动垃圾,那么就会导致并发失败,这时JVM会触发预备方案,启用Serial Old收集器来回收Old区,这时停顿时间就变得更长了。

GC时为什么要暂停用户线程?
首先,如果不暂停用户线程,就意味着期间会不断有垃圾产生,永远也清理不干净。
其次,用户线程的运行必然会导致对象的引用关系发生改变,这就会导致两种情况:漏标和错标。

为什么CMS采用“标记-清除”算法而不采用“标记-整理”算法
因为CMS作为第一款实现用户线程和收集线程并发执行的收集器,当时的设计理念是减少停顿时间,最好是能并发执行。
但是问题来了,如要用户线程也在执行,那么就不能轻易的改变堆中对象的内存地址,不然会导致用户线程无法定位引用对象,从而无法正常运行。而标记整理算法和标记复制算法都会移动存活的对象,这就与上面的策略不符,因此CMS采用的是标记-清除算法。

7. G1收集器

G1(Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

被视为JDK1.7中HotSpot虚拟机的一个重要进化特征,具备以下特点:

  • 分代收集
  • 并行与并发。G1能充分利⽤CPU、多核环境下的硬件优势,使⽤多个CPU(CPU或者CPU核⼼)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执⾏的GC动作,G1收集器仍然可以通过并发的⽅式让java程序继续执⾏。
  • 空间整合。与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
  • 可预测的停顿

G1回收流程

运作分为四个步骤:

  • 初始标记
    这个阶段是STW(Stop the World )的,所有应用线程会被暂停,标记出从GC Root开始直接可达的对象。
  • 并发标记
    从GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时较长。
  • 最终标记
    标记那些在并发标记阶段发生变化的对象,这阶段需要停顿线程,但是可以并行执行。
  • 筛选回收
    暂停用户线程,筛选阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
    G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

G1收集器的内存模型


G1堆内存结构
堆内存会被切分成为很多个固定大小区域(Region),每个是连续范围的虚拟内存。
堆内存中一个区域(Region)的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间最小1M、最大32M,总之是2的幂次方。
默认把堆内存按照2048份均分。

G1堆内存分配
每个Region被标记了E、S、O和H,这些区域在逻辑上被映射为Eden、Survivor和老年代。
存活的对象从一个区域转移(即复制或移动)到另一个区域。区域被设计为并行收集垃圾,可能会暂停所有应用线程。

如上图所示,区域可以分配到Eden、survivor和老年代。此外,还有第四种类型,被称为巨型区域(Humongous Region),Humongous区域是为了那些存储超过50%标准region大小的对象而设计的,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
G1收集器采用“标记-复制”和“标记-整理”。从整体上看是基于“标记-整理”,从局部看,两个region之间是“标记-复制”。

G1的GC模式

  1. YoungGC年轻代收集
    在分配一般对象(非巨型对象)时,当所有eden region使用达到最大阀值并且无法申请足够内存时,会触发一次YoungGC。每次younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。
    YoungGC的回收过程如下:
    • 根扫描,跟CMS类似,Stop the world,扫描GC Roots对象。
    • 处理Dirty card,更新RSet.
    • 扫描RSet,扫描RSet中所有old区对扫描到的young区或者survivor去的引用。
    • 拷贝扫描出的存活的对象到survivor2/old区
    • 处理引用队列,软引用,弱引用,虚引用
  2. mixed gc
    当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。
    G1没有fullGC概念,需要fullGC时,调用serialOld GC进行全堆扫描(包括eden、survivor、o、perm)。

8. ZGC

ZGC(Z Garbage Collector)是一种低延迟的垃圾回收器,是 JDK 11 引入的一项垃圾回收技术。它主要针对大内存、多核心的应用场景,旨在减少垃圾回收带来的停顿时间。

其它

垃圾收集器参数总结