分类与对比

垃圾回收器分类

当我们在讨论垃圾回收器时,往往也会涉及到很多的概念;譬如并行(Parallel)与并发(ConcurrentMinor GCMajor/Full GC

  • 并行指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;并发指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

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

从不同角度分析垃圾回收器,可以将其分为不同的类型:

  • 线程数:分为串行垃圾回收器和并行垃圾回收器。

    • 串行垃圾回收器一次只使用一个线程进行垃圾回收;
    • 并行垃圾回收器一次将开启多个线程同时进行垃圾回收。在并行能力较强的CPU上,使用并行垃圾回收器可以缩短GC的停顿时间;
  • 工作模式:分为并发式垃圾回收器和独占式垃圾回收器。

    • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间;
    • 独占式垃圾回收器(Stop the world)一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束;
  • 碎片处理方式:分为压缩式垃圾回收器和非压缩式垃圾回收器。

    • 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片;
    • 非压缩式的垃圾回收器不进行这步操作;
  • 工作的内存区间:新生代垃圾回收器和老年代垃圾回收器。

垃圾回收器的衡量指标

我们最常用的评价垃圾回收器的指标就是吞吐量与停顿时间:

  • 停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;

  • 高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务;

具体的指标列举如下:

  • 吞吐量:指在应用程序的生命周期内,应用程序所花费的时间和系统总运行时间的比值。系统总运行时间=应用程序耗时+GC耗时。如果系统运行了100minGC耗时1min,那么系统的吞吐量就是(100-1)/100=99%

  • 垃圾回收器负载:和吞吐量相反,垃圾回收器负载指来记回收器耗时与系统运行总时间的比值;

  • 停顿时间:指垃圾回收器正在运行时,应用程序的暂停时间。对于独占回收器而言,停顿时间可能会比较长。使用并发的回收器时,由于垃圾回收器和应用程序交替运行,程序的停顿时间会变短,但是,由于其效率很可能不如独占垃圾回收器,故系统的吞吐量可能会较低;

  • 垃圾回收频率:指垃圾回收器多长时间会运行一次。一般来说,对于固定的应用而言,垃圾回收器的频率应该是越低越好。通常增大堆空间可以有效降低垃圾回收发生的频率,但是可能会增加回收产生的停顿时间;

  • 反应时间:指当一个对象被称为垃圾后多长时间内,它所占据的内存空间会被释放;

  • 堆分配:不同的垃圾回收器对堆内存的分配方式可能是不同的。一个良好的垃圾回收器应该有一个合理的堆内存区间划分;

在对象查找算法的帮助下我们可以找到内存可以被使用的,或者说那些内存是可以回收,更多的时候我们肯定愿意做更少的事情达到同样的目的。

JVM垃圾回收器对比

1999年随JDK1.3.1一起来的是串行方式的Serial GC,它是第一款垃圾回收器;此后,JDK1.4J2SE1.3相继发布。2002226日,J2SE1.4发布;Parallel GCConcurrent Mark Sweep (CMS)GC跟随JDK1.4.2一起发布,并且Parallel GCJDK6之后成为HotSpot默认GC。这三个垃圾回收器也是各有千秋,Serial GC适合最小化地使用内存和并行开销的场景、Parallel GC适合最大化应用程序吞吐量的场景、CMS GC适合最小化中断或停顿时间的场景。上图即展示了多种垃圾回收器之间的关系;不过随着应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有合适的回收器就不能保证应用程序正常进行,而经常造成STW停顿的回收器又跟不上实际的需求,所以才会不断地尝试对搜集器进行优化。Garbage First(G1)GC正是面向这种业务需求所生,它是一个并行回收器,把堆内存分割为很多不相关的区间(Region);每个区间可以属于老年代或者年轻代,并且每个年龄代区间可以是物理上不连续的。

名称 作用域 算法 特性 设置
Serial Serial GC作用于新生代,Serial Old GC作用于老年代垃圾收集 二者皆采用了串行回收与 “Stop-the-World”,Serial使用的是复制算法,而Serial Old使用的是电俄式-标记压缩算法 基于串行回收的垃圾回收器适用于大多数对于暂停时间要求不高的Client模式下的JVM 使用 -XX:+UserSerialGC 手动指定使用Serial回收器执行内存回收任务
Throughput/Parallel Parallel作用于新生代,Parallel Old作用于老年代 并行回收和 “Stop-the-World”,Parallel使用的是复制算法,Parallel Old使用的是标记-压缩算法 程序吞吐量优先的应用场景中,在Server模式下内存回收的性能较为不错 使用 -XX:+UseParallelGC 手动指定使用Parallel回收器执行内存回收任务
CMS,Concurrent-Mark-Sweep 老年代垃圾回收器,又称作 Mostly-Concurrent 回收器 使用了标记清除算法,分为初始标记( Initial-Mark,Stop-the-World )、并发标记( Concurrent-Mark )、再次标记( Remark,Stop-the-World )、并发清除( Concurrent-Sweep ) 并发低延迟,吞吐量较低。经过CMS收集的堆会产生空间碎片,会带来堆内存的浪费 使用 -XX:+UseConcMarkSweepGC 来手动指定使用CMS回收器执行内存回收任务
G1,Garbage First 没有采用传统物理隔离的新生代和老年代的布局方式,仅仅以逻辑上划分为新生代和老年代,选择的将Java堆区划分为2048个大小相同的独立Region 使用了标记压缩算法 基于并行和并发、低延迟以及暂停时间更加可控的区域化分代式服务器类型的垃圾回收器 使用 -XX:UseG1GC 来手动指定使用G1回收器执行内存回收任务

关于标记阶段有几个关键点是值得注意的:

  • 开始进行标记前,需要先暂停应用线程,否则如果对象图一直在变化的话是无法真正去遍历它的。暂停应用线程以便JVM可以尽情地收拾家务的这种情况又被称之为安全点(Safe Point),这会触发一次Stop The World(STW)暂停。触发安全点的原因有许多,但最常见的应该就是垃圾回收了。
  • 暂停时间的长短并不取决于堆内对象的多少也不是堆的大小,而是存活对象的多少。因此,调高堆的大小并不会影响到标记阶段的时间长短。

当标记阶段完成后,GC开始进入下一阶段,删除不可达对象。

Serial GC

串行回收器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。在串行回收器进行垃圾回收时,Java应用程序中的线程都需要暂停,等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行回收器却是一个成熟、经过长时间生产环境考验的极为高效的 回收器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。在HotSpot虚拟机中,使用-XX+UseSerialGC参数可以指定使用新生代串行回收器和老年代串行回收器。当JVMClient模式下运行时,它是默认的垃圾回收器。老年代串行回收器使用的是标记-压缩算法。和新生代串行回收器一样,它也是一个串行的、独占式的垃圾回收器。由于老年代垃圾回收通常会使用比新生代垃圾回 收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行回收器启动,应用程序很可能会因此停顿几秒甚至更长时间。虽然如此,老年代串行回收器可以 和多种新生代回收器配合使用,同时它也可以作为CMS回收器的备用回收器。若要启用老年代串行回收器,可以尝试使用以下参数:-XX:+UseSerialGC:新生代、老年代都使用串行回收器。

Serial GC的工作步骤如下所示:

Serial GC 工作步骤

Parallel GC

Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)Parallel OldParallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的。使用 -XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收回收器,这是一对非常关注吞吐量的垃圾回收器组合,在对吞吐量敏感的系统中,可以考虑使用。参数 -XX:ParallelGCThreads 也可以用于设置垃圾回收时的线程数量。

Parallel GC的工作步骤如下所示:

Parallel GC 工作步骤

CMS GC

CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器,适用于对停顿比较敏感,并且有相对较多存活时间较长的对象(老年代较大)的应用程序;不过CMS虽然减少了回收的停顿时间,但是降低了堆空间的利用率。CMS GC采用了Mark-Sweep算法,因此经过CMS收集的堆会产生空间碎片;为了解决堆空间浪费问题,CMS回收器不再采用简单的指针指向一块可用堆空间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表,当JVM分配对象空间的时候,会搜索这个列表找到足够大的空间来存放住这个对象。另一方面,由于CMS线程和应用程序线程并发执行,CMS GC需要更多的CPU资源。同时,因为CMS标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在CMS回收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。也就是说,CMS不会在老年代满的时候才开始收集。相反,它会尝试更早的开始收集,已避免上面提到的情况:在回收完成之前,堆没有足够空间分配!默认当老年代使用68%的时候,CMS就开始行动了。– XX:CMSInitiatingOccupancyFraction =n来设置这个阀值。

CMS GC工作步骤如下所示:

CMS GC 工作步骤

  • 初始标记(STW initial mark):在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
  • 并发标记(Concurrent marking):这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
  • 并发预清理(Concurrent precleaning):并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代,或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World
  • 重新标记(STW remark):这个阶段会暂停虚拟机,回收器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
  • 并发清理(Concurrent sweeping):清理垃圾对象,这个阶段回收器线程和应用程序线程并发执行。
  • 并发重置(Concurrent reset):这个阶段,重置CMS回收器的数据结构,等待下一次垃圾回收。

G1 GC

G1 GCJDK 1.7中正式投入使用的用于取代CMS的压缩回收器,它虽然没有在物理上隔断新生代与老生代,但是仍然属于分代垃圾回收器;G1 GC仍然会区分年轻代与老年代,年轻代依然分有Eden区与Survivor区。G1 GC首先将堆分为大小相等的Region,避免全区域的垃圾收集,然后追踪每个Region垃圾堆积的价值大小,在后台维护一个优先列表,根据允许的收集时间优先回收价值最大的Region;同时G1 GC采用Remembered Set来存放Region之间的对象引用以及其他回收器中的新生代与老年代之间的对象引用,从而避免全堆扫描。G1 GC的分区示例如下图所示:

随着G1 GC的出现,Java垃圾回收器通过引入Region的概念,从传统的连续堆内存布局设计,逐步走向了物理上不连续但是逻辑上依旧连续的内存块;这样我们能够将某个Region动态地分配给Eden、Survivor、老年代、大对象空间、空闲区间等任意一个。每个Region都有一个关联的Remembered Set(简称RS)RS的数据结构是Hash表,里面的数据是Card Table (堆中每512byte映射在card table 1byte)。简单的说RS里面存在的是Region中存活对象的指针。当Region中数据发生变化时,首先反映到Card Table中的一个或多个Card上,RS通过扫描内部的Card Table得知Region中内存使用情况和存活对象。在使用Region过程中,如果Region被填满了,分配内存的线程会重新选择一个新的Region,空闲Region被组织到一个基于链表的数据结构(LinkedList)里面,这样可以快速找到新的Region

总结而言,G1 GC的特性如下:

  • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力;
  • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况;
  • 分代GCG1依然是一个分代回收器,但是和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;
  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单地标记清理对象。在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片,进而提升内部循环速度。
  • 可预见性:为了缩短停顿时间,G1建立可预存停顿的模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。

G1 GC的工作步骤如下所示:

G1 GC 工作步骤

  • 初始标记(标记一下GC Roots能直接关联的对象并修改TAMS值,需要STW但耗时很短)
  • 并发标记(GC Root从堆中对象进行可达性分析找存活的对象,耗时较长但可以与用户线程并发执行)
  • 最终标记(为了修正并发标记期间产生变动的那一部分标记记录,这一期间的变化记录在Remembered Set Log里,然后合并到Remembered Set里,该阶段需要STW但是可并行执行)
  • 筛选回收(对各个Region回收价值排序,根据用户期望的GC停顿时间制定回收计划来回收)

ZGC

与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障,比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World

上一页
下一页