JVM之垃圾回收器


总的来说,Java 虚拟机的垃圾回收器可以分为四大类别:串行回收器、并行回收器、CMS 回收器、G1 回收器。

1.串行回收器

串行回收器是指使用单线程进行垃圾回收的回收器。因为每次回收时只有一个线程,因此串行回收器在并发能力较弱的计算机上,其专注性和独占性的特点往往能让其有更好的性能表现。

串行回收器可以在新生代和老年代使用,根据不同的堆空间,分为新生代串行回收器和老年代串行回收器。

1.1新生代串行回收器

串行回收器是所有垃圾回收器中最古老的一种,也是 JDK 中最基本的垃圾回收器之一。它主要有两个特点:

  1. 它仅仅使用单线程进行垃圾回收
  2. 它是独占式的垃圾回收方式

在新生代串行回收器中使用的是复制算法。在串行回收器进行垃圾回收时,会产生 Stop-The-World 现象,即其他线程都需要暂停,等待垃圾回收完成。因此在某些情况下,其会造成较为糟糕的用户体验。

“Stop-the-World”现象:在进行垃圾回收时,Java应用程序的所有线程都会被暂停,直到垃圾回收完成。这意味着在这段时间内,应用程序无法继续执行任何任务,会导致应用程序的性能下降和响应时间延长。

1.2老年代串行回收器

在老年代串行回收器中使用的是标记压缩算法。其与新生代串行收集器一样,只能串行、独占式地进行垃圾回收,因此也经常会有较长时间的 Stop-The-World 发生。

优点:可以与多种新生代回收器配合使用,可以作为CMS回收器的备用回收器。若要启用老年代串行回收器,可以尝试以下参数:

  • -XX:UseSerialGC:新生代、老年代都使用串行回收器。
  • -XX:UseParNewGC:新生代使用 ParNew 回收器,老年代使用串行回收器。
  • -XX:UseParallelGC:新生代使用 ParallelGC 回收器,老年代使用串行回收器。

2并行回收器

并行回收器在串行回收器的基础上做了改进,其使用多线程进行垃圾回收。对于并行能力强的机器,可以有效缩短垃圾回收所使用的时间。

根据内存区域的不同,并行回收器也有三个不同的回收器:新生代 ParNew 回收器、新生代 ParallelGC 回收器、老年代 ParallelGC 回收器。

2.1新生代 ParNew 回收器

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

新生代 ParNew 回收器虽然使用了多线程来回收,但它还是独占式的,它的垃圾收集过程中应用程序会全部暂停。但因为其使用多线程进行垃圾回收,因此在并发能力强的 CPU 上,其产生的停顿时间要短于串行回收器。

但在单 CPU 或并能能力弱的系统中,并行回收器效果会因为线程切换的原因,其实际表现反而不如串行回收器。

2.2新生代 Parallel GC 回收器

从表面上看,新生代 Parallel GC 和ParNew是一样的,都是使用复制算法,都是多线程、独占式的收集器。但是,ParallelGC回收器有一个重要特点:它关注系统的吞吐量。

Parallel GC 回收器提供了两个重要参数用于控制系统的吞吐量。

  • -XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间。在 ParallelGC 工作时,其会自动调整响应参数,将停顿时间控制在设置范围内。为了达到目的,其可能会使用较小的堆,但这会导致 GC 较为频繁。
  • -XX:GCTimeRatio:设置吞吐量大小,其实一个 0 - 100 的整数。假设 GCTimeRatio 的值为 n,那么系统将不花费超过 1/(1+n) 的时间用于垃圾手机。比如 GCTimeRatio 值为 19,那么系统用于垃圾收集的时间不超过 1 /(1+19) = 5%。默认情况下,它的取值是 99,即不超过 1% 的时间用于垃圾收集。

除此之外,ParallelGC和PaeNew的另一个不同之处:它有一个自适应 GC 调节策略。在这个模式下,新生代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数都会被自动调节,已达到堆大小、吞吐量、停顿时间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标吞吐量和停顿时间,让虚拟机自己完成调优工作。

2.3老年代 ParallelOldGC 回收器

老年代 ParallelOldGC 回收器也是一种多线程并发的回收器,与新生代 ParallelGC 收集器一样,其也是注重吞吐量的收集器,只不过其是作用于老年代。

ParallelOldGC 回收器使用的是标记压缩算法,只有在 JDK 1.6 中才可以使用。我们可以使用-XX:UseParallelOldGC参数在新生代中使用 ParallelGC 收集器,在老年代中使用 ParallelOldGC 收集器。参数 -XX:ParallelGCThreads也可以用于设置垃圾回收时的线程数量。


3.一心多用都不落下:CMS回收器

与 ParallelGC 和 ParallelOldGC 不同,CMS 回收器主要关注系统停顿时间。CMS 回收器全称为 Concurrent Mark Sweep,意为并发标记清除从名称可以知道,它使用了标记清除算法,同时也是使用多线程并行回收的垃圾回收器。

3.1工作步骤

CMS 的主要工作步骤有:初始标记、并发标记、预清理、重新标记、并发清除和并发充值。其中初始标记和重新标记是独占系统资源的,而其他阶段则可以和用户线程一起执行。

根据标记清除算法,初始标记、并发标记和重新标记都是为了标记出需要回收的对象。并发清除则是在标记完成后,正式回收垃圾对象。并发重置是指在垃圾回收完成后,重新初始化CMS数据结构和数据,为下一次垃圾回收做好准备。

触发时机:由于CMS回收器不是独占式的回收器,在CMS回收过程中应用程序仍然在工作,会不断产生新垃圾,这些新垃圾在CMS回收过程中是无法清除的,所以CMS不会等待堆内存饱和才进行回收,而是内存使用率达到某个阈值就开始回收。

兜底方案:如果CMS执行期间内存不足了,CMS就会失败,虚拟机会启用老年代串行回收器进行垃圾回收。此时应用程序会完全中断,直到垃圾回收完成。

我们之前说过标记清除算法的缺点是会产生内存碎片,因此 CMS 回收器会产生较多内存碎片。我们可以使用 XX:+UseCMSCompactAtFullCollection 参数让 CMS 在完成垃圾回收后,进行一次内存碎片整理。使用 -XX:CMSFullGCsBeforeCompaction 参数设置进行多少次 CMS 回收后,进行一次内存压缩。

3.2 有关Class的回收

如果希望使用 CMS 回收 Perm 区,那么则可以打开 -XX:+CMSClassUnloadingEnabled 开关。打开该开关后,如果条件允许,那么系统会使用 CMS 的机制回收 Perm 区 Class 数据。


4.G1回收器

G1 回收器(Garbage-First)是 JDK 1.7 中使用的全新垃圾回收器,从长期目标来看,其是为了取代 CMS 回收器。

G1 回收器拥有独特的垃圾回收策略,和之前所有垃圾回收器采用的垃圾回收策略不同。从分代看,G1 依然属于分代垃圾回收器,它会区分新生代和老年代,依然会有eden区和survivor区,但从堆的结构上看,它并不要求整个eden区、新生代或者老年代都连续。但它最大的改变是使用了分区算法,从而使得 Eden 区、Survivor 区和老年代等各块内存不必连续。

在 G1 回收器之前,所有的垃圾回收器其内存分配都是连续的一块内存,如下图所示:

而在 G1 回收器中,其将一大块的内存分为许多细小的区块,从而不要求内存是连续的。

从上图可以看到,每个Region被标记了 E、S、O 和 H,说明每个 Region 在运行时都充当了一种角色。所有标记为 E 的都是 Eden 区的内存,它们散落在内存的各个角落,并不要求内存连续。同理,Survivor 区、老年代(Old)也是如此。

从上图我们还可以看到 H 是以往算法中没有的,它代表 Humongous。这表示这些 Region 存储的是巨型对象(humongous object,H-obj),当新建对象大小超过 Region 大小一半时,直接在新的一个或多个连续 Region 中分配,并标记为 H。

堆内存中一个 Region 的大小可以通过 -XX:G1HeapRegionSize 参数指定,大小区间只能是1M、2M、4M、8M、16M 和 32M,总之是2的幂次方。如果G1HeapRegionSize 为默认值,即把设置的最小堆内存按照2048份均分,最后得到一个合理的大小。

4.1工作步骤

G1 收集器的收集过程主要有四个阶段:

  • 新生代 GC
  • 并发标记周期
  • 混合收集
  • 如果需要,可能进行 FullGC

新生代 GC 与其他垃圾收集器的类似,就是清空 Eden 区,将存活对象移动到 Survivor 区,部分年龄到了就移动到老年代。

并发标记周期和CMS有点类似,分为以下几步:初始标记、根区域扫描、并发标记、重新标记、独占清理、并发清理阶段。其中初始标记、重新标记、独占清理是独占式的,会引起停顿。并且初始标记会引发一次新生代 GC。在这个阶段,所有将要被回收的区域会被 G1 记录在一个称之为 Collection Set 的集合中。

在并发标记周期中,虽然有部分对象被回收,但是总体上说,回收比例上相当低的,在并发标记周期后,G1已经明确知道哪些区域有较多的垃圾,在混合回收阶段就针对这些区域进行回收。G1 回收器的名字 Garbage First 就是这个意思,垃圾优先处理的意思。这个阶段叫做混合回收,是因为在这个阶段既会执行正常的新生代GC,有会对一些老年代区域进行回收,它同时处理了新生代和老年代。

必要时进行 Full GC。和CMS类似,当在回收阶段遇到内存不足时,G1 会停止垃圾回收并进行一次 Full GC,从而腾出更多空间进行垃圾回收。


5.垃圾回收器对比

回收器 线程模型 停顿时间 内存模型 适用场景 jdk默认版本
串行 单线程 长 分代 客户端/小内存 1.3之前
并行 多线程 长 分代 高吞吐量计算任务 8(默认)
CMS 多线程 短 分代 低延迟服务 8可选
G1 多线程 可控 分代+Region 大堆、低延迟 9+(默认)

文章作者: Fuchanglai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Fuchanglai !
赏
  目录