JVM_垃圾收集器

判断对象是否存活

引用计数法

给对象添加一个引用计数器,当有一个新的引用指向这个对象,就在计数器加一,引用失效减一。计数器为 0 时,认为该对象不会被再使用。

缺点:无法解决对象循环引用,如果两个对象互相引用,并且这两个对象都已不再使用。那么它们永远不会被回收。

可达性分析算法

使用 GC Roots 判断对象是否可达,通过一系列的”GC Roots” 对象作为起始结点,从这些结点到对象间的路径叫做引用链,当一个对象到 GC Roots 没有任何引用链,判定为可回收的对象

ANT2ss.png

引用
  • 引用划分:强引用、软引用、弱引用、虚引用

垃圾收集算法

标记-清除法

标记-清除法分为标记和清除两个阶段:首先在堆中标记这个对象是否应该被回收,标记完成后统一回收被标记的对象。

缺点:

  • 标记-清除这两个过程的效率不高
  • 空间问题:清除后会产生许多不连续的内存碎片,导致后续分配大对象时,仍旧无法找到空间,再次触发 GC
复制算法

将内存划分为相等的两块,当这块使用完,则把存活的对象复制到另一块。然后清理使用过的空间。

缺点:将内存缩小为原来的一半

标记-整理算法

首先标记出需要回收的对象,再将存活的对象都向一端移动,然后直接清理其余内存。

分代收集算法

根据对象的存活周期,把内存划分为几块,在不同的区域使用不同的回收算法。
一般是分为新生代和老年代,新生代会有大量的对象死亡,可以使用复制算法,老年代对象存活率较高,可以使用标记-清除,或者标记-整理算法。

HotSpot 的算法实现

安全点

程序执行时并非在所有地方都能暂停下来执行 GC ,必须在安全点。如何在 GC 发生时,让所有线程都跑到最近的安全点:

  • 抢先式中断:可以理解为线程被动中断,在 GC 发生时,首先中断所有线程,如果有线程没有到达安全点,则恢复线程,使它跑到安全点
  • 主动式中断:线程主动中断,不直接对线程操作,而是设置一个中断标志,线程主动轮询这个标志,一旦发现需要中断,则自动中断挂起
安全区域

指的是在一段代码内,引用关系不会发生变化,在这段区域内的任何地方都可以发生中断。

垃圾收集器

Aaz2Ie.png

Serial 收集器

单线程收集器,不能与用户线程同时运行,必须暂停其他所有线程,直到收集结束。

AdSXTO.png

优点:

  • 简单高效,没有线程交互的开销。
  • 在用户的应用场景中,分配给虚拟机的内存一般不会很大,因此需要收集的内存不多,通常停顿时间较短。
    因此,仍然是 client 模式下默认的新生代收集器
ParNew 收集器

是 Serial 收集器的多线程版本(多个 GC 线程),但在单 CPU 的环境下,因为线程交互的开销,可能比 Serial 性能更差,它默认开启的收集线程数与 CPU 数量相同。

AdPDH0.png

Parallel Scavenge 收集器

该收集器的关注点在于,达到一个可控制的吞吐量

吞吐量 = 用户代码运行时间/(用户代码运行时间+垃圾收集时间)

Serial Old 收集器

Serial 的老年代版本,主要给 client 模式下的虚拟机使用。

Parallel Old 收集器

Parallel Scavenge 的老年代版本,在注重吞吐量以及 CPU 资源敏感的场合,可以优先考虑 Parallel Scavenge + Parallel Old

CMS 收集器

以获取最短 GC 停顿时间为目标的收集器,它将垃圾收集分成了几个步骤,而其中耗时最长的步骤都可以与用户线程一起工作,因此降低了 GC 停顿时间。主要优点是:并发收集、低停顿

缺点:

  • 无法处理“浮动垃圾”,即并发运行的用户线程产生的垃圾。
  • 使用标记-清除算法产生的空间碎片

步骤:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清理

AdFnOA.png

G1 收集器

G1 是面向服务端应用的垃圾收集器,特点如下:

  • 并行与并发:缩短停顿时间,达到并发
  • 分代收集:采用不同的方式收集不同对象区域
  • 空间整合:收集后会规整内存
  • 可预测的停顿:化整为零,避免全堆扫描,优先回收价值最大的区域

步骤:

header 1 header 2
row 1 col 1 row 1 col 2
row 2 col 1 row 2 col 2
  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

AdkGAx.png