Java垃圾回收器

来源:互联网 发布:potplayer for mac 编辑:程序博客网 时间:2024/05/18 00:43

最主流的四个垃圾回收器分别是@1Serial GC、@2Throughput/Parallel GC、@3CMS GC、@4G1 GC。

名 称

作用域

算法

特性

设置

@1

Serial GC 作用于新生代,Serial Old GC 作用于老年代垃圾收集

二者皆采用了串行回收与 "Stop-the-World",Serial 使用的是复制算法,而 Serial Old 使用的是电俄式-标记压缩算法

适用于大多数对于暂停时间要求不高的 Client 模式下的 JVM

 -XX:+UserSerialGC手动指定使用 Serial 回收器执行内存回收任务

@2

Parallel 作用于新生代,Parallel Old 作用于老年代

并行回收和 "Stop-the-World",Parallel 使用的是复制算法,Parallel Old 使用的是标记-压缩算法

程序吞吐量优先的应用场景中,在 Server 模式下内存回收的性能较为不错

 -XX:+UseParallelGC 手动指定使用 Parallel 回收器执行内存回收任务

@3

Concurrent-Mark-Sweep,老年代垃圾回收器,又称作 Mostly-Concurrent 回收器

使用了标记清除算法,分为初始标记( Initial-Mark,Stop-the-World )、并发标记( Concurrent-Mark )、再次标记( Remark,Stop-the-World )、并发清除( Concurrent-Sweep )

并发低延迟,吞吐量较低。经过CMS收集的堆会产生空间碎片,会带来堆内存的浪费

 -XX:+UseConcMarkSweepGC来手动指定使用 CMS 回收器执行内存回收任务

@4

Garbage First,没有采用传统物理隔离的新生代和老年代的布局方式,仅仅以逻辑上划分为新生代和老年代,选择的将 Java 堆区划分为 2048 个大小相同的独立 Region 块

使用了标记压缩算法

基于并行和并发、低延迟以及暂停时间更加可控的区域化分代式服务器类型的垃圾回收器

 -XX:UseG1GC 来手动指定使用 G1 回收器执行内存回收任务

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

  • 开始进行标记前,需要先暂停应用线程,否则如果对象图一直在变化的话是无法真正去遍历它的。暂停应用线程以便 JVM 可以尽情地收拾家务的这种情况又被称之为安全点(Safe Point),这会触发一次STW暂停。触发安全点的原因有许多,但最常见的应该就是垃圾回收了。

  • 暂停时间的长短并不取决于堆内对象的多少也不是堆的大小,而是存活对象的多少。因此调高堆的大小并不会影响到标记阶段的时间长短。

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

Serial GC

串行回收器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。在串行回收器进行垃圾回收时,Java 应用程序中的线程都需要暂停,等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行回收器却是一个成熟、经过长时间生产环境考验的极为高效的 回收器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。

在 HotSpot 虚拟机中,使用-XX:+UseSerialGC 参数可以指定使用新生代串行回收器和老年代串行回收器。当 JVM 在 Client 模式下运行时,它是默认的垃圾回收器。老年代串行回收器使用的是标记-压缩算法。和新生代串行回收器一样,它也是一个串行的、独占式的垃圾回收器。由于老年代垃圾回收通常会使用比新生代垃圾回收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行回收器启动,应用程序很可能会因此停顿几秒甚至更长时间。虽然如此,老年代串行回收器可以 和多种新生代回收器配合使用,同时它也可以作为 CMS 回收器的备用回收器。若要启用老年代串行回收器,可以尝试使用以下参数:-XX:+UseSerialGC: 新生代、老年代都使用串行回收器。

ParNew GC

并行回收器是工作在新生代的垃圾回收器,它只简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。

并行回收器 也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。开启并行回收器可以使用参数-XX:+UseParNewGC,该参数设置新生代使用并行回收器,老年代使用串行回收器。老年代的并行回收回收器也是一种多线程并发的回收器。和新生代并行回收回收器一样,它也是一种关注吞吐量的回收器。老年代并行回收回收器使用标记-压缩算法,JDK1.6 之后开始启用。

Parallel GC

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

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 工作步骤如下所示:

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

随着 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拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况;

  • 分代GC:G1依然是一个分代回收器,但是和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;

  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单地标记清理对象。在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片,进而提升内部循环速度。

  • 可预见性:为了缩短停顿时间,G1建立可预存停顿的模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。

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

  • 初始标记(标记一下GC Roots能直接关联的对象并修改TAMS值,需要STW但耗时很短)

  • 并发标记(从GC Root从堆中对象进行可达性分析找存活的对象,耗时较长但可以与用户线程并发执行)

  • 最终标记(为了修正并发标记期间产生变动的那一部分标记记录,这一期间的变化记录在Remembered Set Log 里,然后合并到Remembered Set里,该阶段需要STW但是可并行执行)

  • 筛选回收(对各个Region回收价值排序,根据用户期望的GC停顿时间制定回收计划来回收)


原创粉丝点击