JVM: 垃圾收集器

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

HotSpot虚拟机的垃圾收集器如图所示,图中展示了7种分别作用于新生代和老年代的收集器,如果两个收集器之间存在连线,就说明可以搭配使用。

并发和并行

  • 并行(Parallel):并行就是同时执行。
    多条垃圾收集线程并行工作,但用户线程仍处于等待状态。
  • 并发(Concurrent):多个操作可以在重叠的时间段内运行。
    用户线程与垃圾收集线程同时执行,但不一定是并行执行,可能会交替执行。

并行是指物理上同时执行,并发是指能够让多个任务在逻辑上交织执行的程序设计。

Minor GC和Full GC

  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC):指发生在老年代的GC。Major GC的速度一般会比Minor GC慢10倍以上。
  • 老年代GC(Major GC/Full GC):指发生在新生代和老年代的GC,由于出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程),所以Major GC和Full GC区别不大。

吞吐量

吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。

Serial收集器(新生代)

Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

Stop The World

应用场景:Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。

ParNew收集器(新生代)

ParNew收集器是Serial收集器的多线程版本。

除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样。

应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。

Parallel Scavenge收集器(新生代)

Parallel Scavenge收集器,也是新生代使用复制算法的收集器,又是并行的多线程收集器。
但是Parallel Scavenge收集器和其他收集器的关注点不同,它的目标是达到一个可控制的吞吐量(Throughput),所以也经常称为“吞吐量优先”收集器。

Serial Old收集器(老年代)

Serial Old收集器是Serial收集器的老年代版本,同样也是一个单线程收集器,使用“标记-整理”算法。

应用场景:Serial Old收集器主要是给虚拟机在Client模式下使用。在Server模式下的用途:一个是与Parallel Scavenge收集器搭配使用,另一个是作为CMS收集器的后备预案。

Parallel Old收集器(老年代)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

直到Parallel Old收集器出现,“吞吐量优先”收集器才有了名副其实的应用组合,与Parallel Scavenge收集器一起使用。

CMS收集器(老年代)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法。

目前只有Serial收集器ParNew收集器能与CMS收集器配合工作。
在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器——CMS收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。

CMS的运作过程分为4个步骤:

  1. 初始标记(CMS initial mark)

    Stop The World

    初始标记仅仅是标记一下GC Roots能直接关联到的对象,速度很快。

  2. 并发标记(CMS concurrent mark)
    并发标记就是进行GC Roots Tracing的过程。
  3. 重新标记(CMS remark)

    Stop The World

    重新标记是为了修正并发标记期间因用户程序继续运作而导致目标标记产生表动的那一部分对象的标记记录。

  4. 并发清除(CMS concurrent sweep)

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都是和用户线程一起执行工作的,所以,从总体上说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

G1收集器

G1(Garbage-First)是一款面向服务端应用的垃圾收集器,在 JDK 1.7 中正式使用,采用“分区”算法。它是为了替换掉JDK 1.5中发布的CMS收集器。

特点

与其他GC收集器相比,G1具备如下特点。

  • 并行与并发
    G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

  • 分代收集
    与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

  • 空间整合
    与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。

  • 可预测的停顿
    这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。(G1在后台维护一个优先列表,优先回收价值最大的Region

收集过程

G1收集器的运作大致可划分为以下几个步骤:

  1. 初始标记(Initial Marking)
    初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。

  2. 并发标记(Concurrent Marking)
    并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。

  3. 最终标记(Final Marking)
    最终标记阶段是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。

  4. 筛选回收(Live Data Counting and Evacuation)
    筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。