JAVA GC 分代垃圾收集过程及各种GC的特点

本文详细介绍了JVM内存管理的三大组件——堆、JIT编译器和垃圾回收器,特别是垃圾回收器的工作原理和不同算法,如分代收集、可达性分析等。探讨了各种垃圾收集器,如SerialGC、ParallelGC、CMS、G1、EpsilonGC、ShenandoahGC和ZGC的特点和适用场景。同时,讲解了如何根据应用需求选择合适的GC策略,并提供了相关配置参数示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 JVM性能相关的组件

在调优性能时,JVM的如下三个组件是重点关注的:

  • 堆:存储对象数据的地方,该区域由启动时选择的垃圾收集器管理。大多数调优选项都与堆大小设置与选择的垃圾回收器相关
  • JIT编译器:对性能也有很大的影响,但是很少需要对新版本的JVM进行调优。
  • GC【垃圾回收器】:需要根据业务,选择最适合的GC

在这里插入图片描述

2 分代垃圾收集

2.1 垃圾收集算法

常见的垃圾收集算法大致分为以下两种:引用计数法和可达性分析算法,目前虚拟机都采用可达性算法

引用计数法

基本思路:每一个对象上记录这个对象被引用的次数,只要有任何一个对象引用了次对象,这个对象的计数器就+1,取消对这个对象的引用时,计数器就-1。任何一个时刻,如果该对象的计数器为0,那么这个对象就是可以回收的。但是这种算法有个弊端:无法处理循环引用的情况,见下图

在这里插入图片描述
在栈上,在method中执行完两个set后,method方法结束,图中两条红线引用消失,可以看到留下两个对象在堆内存中循环引用,但此时已经没有地方在用他们了,造成内存泄漏。两个对象不能被GC回收

class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }
}

class B {
    private A a = new A();
    public void setA(A a) {
        this.a = a;
    }
}

public void method() {
    A a = new A();
    B b = new B();
    a.setB(b);
    b.setA(a);
}

可达性分析算法

在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。
该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。
常见的GC Roots有如下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
- 被同步锁持有的对象。

在这里插入图片描述
从图上看出,对象实例1、2、4、6都具有GC Roots可达性,即存活对象,不能被GC回收。
而对于对象实例3、5虽然相互引用,但没有任何一个GC Roots与之相连,属于GC Roots不可达对象,即GC需要回收的垃圾对象。

2.2 分代垃圾收集过程

  1. 任何新对象都被分配到eden空间。两个survivor空间在启动时都是空的。
    在这里插入图片描述
  2. 当eden空间填满时,会触发一个小的垃圾收集 Young GC
    在这里插入图片描述
  3. 被引用的对象被移动到第一个survivor空间。清除eden空间后,未被引用的对象将被删除。
    在这里插入图片描述
  4. 在下一个 minor GC中,伊甸园空间也发生了同样的事情。未引用的对象被删除,引用的对象被移动到幸存者空间。但是它们被移动到第二个幸存者空间(S1)。此外,来自第一个survivor空间(S0)的上一次minor GC的对象的年龄增加并移动到S1。一旦所有幸存的对象被移动到S1, S0和eden都将被清除。注意现在在幸存者空间中有不同年龄的对象。
    在这里插入图片描述
  5. 在下一个minor GC中,重复相同的过程。然而,这一次幸存者空间切换。引用的对象被移动到S0。保存下来的对象年龄增加。eden和S1被清除。
    在这里插入图片描述
  6. 在一次minor GC之后,当老化对象达到某个年龄阈值(如8)时,它们将从年轻代提升到年老代。
    可通过如下2个参数控制对象何时提升到老年代:
    • -XX:PreternureSizeThreshold 直接晋升老年代的对象大小,设置了这个参数后,大于这个参数的对象直接在老年代进行分配。
    • -XX:MaxTenuringThreshold 晋升老年代的对象年龄,对象在每一次Minor GC后年龄增加一岁,超过这个值后进入到老年代。默认值是15。
      在这里插入图片描述
  7. 随着minor gc继续发生,对象将继续被提升到老的代空间。
    在这里插入图片描述
  8. 这几乎涵盖了年轻一代的整个过程。最后,将对老的代执行一次大的GC,以清除和压缩该空间。
    在这里插入图片描述

2.3 minor GC与major GC

在这里插入图片描述

3 垃圾回收器

3.1 Serial GC

Serial GC是JAVA 5、6中默认的GC,minor gc和 major gc都是串行完成的(使用单个虚拟CPU)。
Serial GC使用标记-压缩收集方法,此方法将旧内存移到堆的开头,以便在堆的末尾将新内存分配到单个连续内存块中。这种内存压缩使得向堆分配新内存块的速度更快。
Serial GC是最基本、历史最悠久的收集器,它是一个单线程收集器。一旦回收器开始运行时,整个系统都要停止。Client模式下默认开启,其他模式默认关闭。

在这里插入图片描述
用法:
-XX:+UseSerialGC
这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法。

-XX:+UseParNewGC
ParNew收集器是Serial收集器的多线程版本,在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法。
操作系统是多核CPU上效果明显,单核CPU建议使用串行回收器。打印GC详情时ParNew标识着使用了ParNewGC回收器。默认关闭。

3.2 Parallel GC

Parallel GC使用多个线程执行年轻代垃圾收集,是JVM中GC的默认实现,也称为吞吐量收集器。
并行收集器适用于在多处理器或多线程硬件上运行的具有中型到大型数据集的应用程序。

运行并行GC还会导致“停止世界事件”,并导致应用程序冻结,当需要完成大量工作且可以接受长时间暂停时,可以使用它,例如运行批处理作业。
在这里插入图片描述
吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机运行100分钟,垃圾收集花费1分钟,那吞吐量就99%

默认情况下,在具有N个cpu的主机上,并行垃圾收集器在收集中使用N个垃圾收集器线程。垃圾收集器线程的数量可以通过命令行选项来控制: -XX:ParallelGCThreads= = <所需数量>

-XX:+UseParallelGC

表示新生代使用Parallel收集器,并提供如下参数用于控制吞吐量:

  • -XX:MaxGCPauseMillis:设置最大停顿时间,值是一个大于0的毫秒数,收集器将尽力保证垃圾回收时间不超过设定值,系统运行的需要回收的垃圾总量是固定的,缩短停顿时间的同时会增大回收频度。
  • -XX:GCTimeRatio:用户控制垃圾回收时间占比,它运行的参数值是0-100的整数,如果参数设置为19,代表最大GC时间占总时间的5%(1/(1+19))。
  • -XX:UseAdaptiveSizePolicy:JVM会根据实际运行情况动态调整新生代大小、新生代和s区比例、晋升老年代对象大小等细节参数。

-XX:+UseParallelOldGC

新生代和老年代都使用并行收集器。打印出的GC会带PSYoungGen、ParOldGen关键字。
HotSpot只在老一代中进行压缩。HotSpot中的年轻一代被认为是一个拷贝收集器;因此不需要压缩。
这是自Java 7u4以来并行GC的默认版本

Compacting describes the act of moving objects in a way that there are no holes between objects. After a garbage collection sweep, there may be holes left between live objects. Compacting moves objects so that there are no remaining holes. It is possible that a garbage collector be a non-compacting collector. Therefore, the difference between a parallel collector and a parallel compacting collector could be the latter compacts the space after a garbage collection sweep. The former would not.

3.3 CMS

Concurrent Mark Sweep 并发标记清除,即使用CMS收集器 ConcMarkSweepGC
它试图通过与应用程序线程并发执行大部分垃圾收集工作来最小化由于垃圾收集而导致的暂停。通常并发低暂停收集器不会复制或压缩活动对象。垃圾收集是在不移动活动对象的情况下完成的。如果碎片成为一个问题,分配一个更大的堆。

它使用的是 标记清除算法,运作过程为四个步骤,分别是 初始标记—并发标识—重新标记—并发清除。它是老年代的收集算法,新生代使用ParNew收集算法。默认关闭

CMS的缺点对服务器CPU资源较为敏感,在并发标记时会降低吞吐量。它使用的标记清除算法也会产生大量空间碎片,空间碎片的存在会加大Full GC的频率,虽然老年代还有足够的内存,但是因为内存空间连续,不得不进行Full GC。

CMS收集器比其他gc使用更多的CPU。如果可以分配更多的CPU以获得更好的性能,那么CMS垃圾收集器是比并行收集器更好的选择。CMS GC中不执行压缩。
在这里插入图片描述

通过 -XX:+UseConcMarkSweepGC 设置使用CMS后,可以通过以下参数调整CMS的行为:

  • -XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长。仅在使用CMS收集器时生效。
  • -XX:ParallelCMSThreads 设置并行GC时进行内存回收的线程数量

3.4 G1

Java 7中提供,用于替换CMS收集器。G1是一种并行、并发、递增压缩的低暂停垃圾收集器,它的布局与前面描述的其他垃圾收集器截然不同。

它像CMS一样是并行和并发的,但是它的工作原理与较老的垃圾收集器非常不同。

虽然G1也分代,但它没有针对年轻代、老年代的单独区域。相反,每个代都是一组区域,允许以灵活的方式调整年轻代的大小。

它将堆划分为一组大小相等的区域(1MB到32MB—取决于堆的大小),并使用多个线程扫描它们。在程序运行期间,一个区域可以是一个老区域,也可以是一个年轻区域。

标记阶段完成后,G1知道哪些区域包含最多的垃圾对象。如果用户对最短的暂停时间感兴趣,G1可以选择只疏散几个区域。如果用户不担心暂停时间,或者已经声明了一个相当大的暂停时间目标,G1可能会选择包含更多区域。

由于G1GC识别出垃圾最多的区域,并首先对该区域执行垃圾收集,因此它被称为Garbage First。
在这里插入图片描述
除了Eden, Survivor和Old内存区域,G1GC中还有另外两种类型的区域:

  • Humongous—用于大尺寸对象(大于堆大小的50%)
  • 可用空间-未使用或未分配的空间

使用G1垃圾收集器的JVM参数是**-XX:+UseG1GC**

3.5 Epsilon GC

Epsilon是一个不做任何事情(no-op)的垃圾收集器,作为JDK 11的一部分发布。它处理内存分配,但不实现任何实际的内存回收机制。一旦可用的Java堆耗尽,JVM就会关闭。

它可以用于超延迟敏感的应用程序,开发人员确切地知道应用程序的内存占用,甚至(几乎)拥有完全没有垃圾的应用程序。否则,不建议在任何其他场景中使用Epsilon GC。

使用Epsilon垃圾收集器的JVM参数是**-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC**.

3.6 Shenandoah GC

Shenandoah是作为JDK 12的一部分发布的一个新的GC。Shenandoah相对于G1的关键优势是,它与应用程序线程并发执行更多的垃圾收集周期工作。G1只能在应用程序暂停时清空它的堆区域,而Shenandoah可以与应用程序同时重新定位对象。

Shenandoah可以压缩活动对象,清除垃圾,并在检测到空闲内存后几乎立即将RAM释放回操作系统。由于所有这些都是在应用程序运行时并发发生的,Shenandoah是CPU密集型的

使用Epsilon垃圾收集器的JVM参数是:-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

3.7 ZGC

ZGC是作为JDK 11的一部分发布的另一个GC,在JDK 12中得到了改进。它适用于需要低延迟(少于10毫秒的暂停)和/或使用非常大的堆(几万亿字节)的应用程序。

ZGC的主要目标是低延迟、可伸缩性和易用性。为了实现这一点,ZGC允许Java应用程序在执行所有垃圾收集操作时继续运行。默认情况下,ZGC未提交未使用的内存并将其返回给操作系统。

因此,通过提供极低的暂停时间(通常在2ms内),ZGC比其他传统gc带来了显著的改进。

Shenandoah和ZGC都计划成为产品特性,并在JDK 15中走出实验阶段

4 如何选择GC

如果应用程序没有严格的暂停时间要求,你应该只运行你的应用程序并允许JVM选择正确的收集器。
大多数情况下,默认设置应该可以正常工作。如果有必要,可以调整堆大小以提高性能。如果性能仍然不能满足您的目标,您可以根据应用程序的需求修改收集器:

GC适用场景
Serial如果应用程序有一个小的数据集(大约100 MB)和/或它将运行在没有暂停时间要求的单个处理器上
Parallel如果峰值应用程序性能是优先级,并且没有暂停时间要求,或者一秒或更长时间的暂停是可以接受的
CMS/G1如果响应时间比总体吞吐量更重要,那么垃圾收集暂停时间必须短于大约一秒
ZGC如果响应时间具有高优先级,并且/或您正在使用一个非常大的堆

5. 配置参数

  • 获取到完整的机器名 -Djava.net.preferIPv4Stack=true
  • 打印GC日志 -Xloggc:/var/log/hive/hiveserver2-gc-%t.log -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M
  • 打印OOM日志 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/hive/hs2_heapdump.hprof

参考

Java Garbage Collection Basics
Garbage Collection in Java – What is GC and How it Works in the JVM

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

enjoy编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值