跳至主要內容

Java 21 新特性:分代ZGC

会敲代码的程序猿原创JavaJava Features大约 5 分钟

Java 21 新特性:分代ZGC(Generational ZGC)

Java以其垃圾回收机制而闻名。这是它的主要优势之一,但也可能是许多头疼的根源。

垃圾收集(Garbage Collection)

在Java中,垃圾收集器负责释放堆内存,堆内存是存储Java对象的地方。 这有助于防止内存泄漏并确保有效的资源使用,否则,程序会抛出OutOfMemoryError异常。

垃圾收集open in new window”的概念本质上是自动内存管理, 这可能导致如下潜在的错误:

  1. 需要时间来清理和重新排列内存,引入了运行时开销,超出了程序员的控制。
  2. GC运行的实际点通常是不确定的,对于高吞吐量内存消耗大的应用,可能会长时间的“GC暂停
  3. 讽刺的是,GC的非确定性也是它的优点之一,我们不必担心内存是何时或如何释放的,它将自动发生

有三种主要的自动内存管理技术:

  1. 引用计数(ReferenceCountingopen in new window
  2. 标记和清除(MarkAndSweepopen in new window
  3. 复制(StopAndCopyopen in new window

不同语言如何管理内存

  • C/C++:手动管理内存,程序员负责分配和释放内存
  • Objective-C 和 Swift:引入了自动引用计数(ARC),但仍然需要手动释放内存
  • Rust:使用仿射类型系统open in new window 而不是GC,引入了所有权和借用,编译器在编译时检查内存安全性
  • Kotlin:与Java类似,但引入了Kotlin/Native,允许手动内存管理
  • Java、Pythonopen in new window 、Go、C#open in new window 、JavaScript:自动内存管理,垃圾收集器负责释放内存

HotSpot JVM垃圾收集器

内存管理有许多不同的方法,并且没有“最好”的方法。 即使在一种语言/运行时中,也可以有不止一种垃圾收集方法,JVM就是一个很好的例子。

与单一的GC不同,HotSpot JVMopen in new window有5个GC可供选择:

  • Garbage-First Collector(G1)(Java 9后的默认选项)
  • Serial Collector
  • Parallel Collector
  • Concurrent Mark Sweep (CMS) Collector(Java 9中已弃用)
  • Shenandoah GC(Java 12+)
  • Z Garbage Collector(Java 15中可用于生产)

此外,不要忘记还有其他的JDK实现!

如何选择JVM GC

许多语言只提供了一种垃圾收集方法,而Java之所以提供多种GC选项,取决于您的应用程序对于“全局停顿”事件和总体暂停时间的容忍程度。

GC算法主要关注三个指标:

  1. 吞吐量:应用程序的运行时间与GC时间的比率
  2. 延迟:GC暂停时间
  3. 内存占用:GC对堆内存的使用

与许多问题一样,您无法为所有这些问题进行优化,因此每个GC都需要在它们之间找到平衡点。以下是一些场景及其匹配的GC作为起点:

垃圾收集器场景
Serial小数据集 (最大~100 MB)
资源有限 (例如单核)
暂停时间短
Parallel多核系统上的峰值性能
非常适合高计算负载
暂停时间 > 1秒是可以接受的
G1
CMS
响应时间 > 吞吐量
堆内存较大
暂停时间 < 1秒
Shenandoah尽量减少暂停时间
可预测的延迟
ZGC响应时间是高优先级的,和/或
非常大的堆内存
Epsilon GC性能测试和故障排除

每种方法都有自己的优点和缺点,这在很大程度上取决于应用程序的需求和可用资源。

分代ZGC

Java 11引入的实验性功能ZGC是一种非分代的垃圾收集方法。 尽管如此,它在GC暂停时间方面仍然带来了显著改进,至少在资源足够的情况下,可以比并发线程消耗内存更快地回收内存。 缺点是,它将所有对象存储在一起,而不考虑年龄,因此每个周期都会收集所有对象。

generational hypothesisopen in new window 观察到年轻对象年长对象更有可能“早逝”,于是产生了分代假设。

Java 21 中,分代 ZGC 将堆分为两个逻辑代:一个用于最近分配的对象,另一个用于长期存活对象。 GC 可以专注于更频繁地收集年轻(最近分配)且更有前途(可能长期存在)的对象,而不会增加GC暂停时间,将其保持在 1 毫秒以下。

分代ZGC与非分代ZGC相比的关键优势:

  • 减少分配停滞的风险
  • 降低堆内存开销要求
  • 减少垃圾回收CPU开销

此外,目标是在保留非分代方法已有优势的基础上实现这些优势:

  • 暂停时间保持在在 1 毫秒以下
  • 支持多达数万TB的堆大小
  • 最少的手动配置

为了保持最后一点,新的GC不需要手动配置代的大小、GC使用的线程数,或对象在年轻代中停留的时间。

如何使用 JVM GC

在Java 21中,分代ZGC是默认的垃圾收集器。为了顺利过渡,分代ZGC将与非分代ZGC一起提供,您可以通过以下方式进行配置:

# 启用ZGC(默认为非分代)
$ java -XX:+UseZGC

# 使用分代ZGC
$ java -XX:+UseZGC -XX:+ZGenerational

如果您需要关闭分代ZGC,可以通过将加号(+)替换为减号(-)来实现:

# 不使用分代ZGC
$ java -XX:+UseZGC -XX:-ZGenerational

计划在更晚的版本中完全删除非分代ZGC。