Java 性能最后一个领域:去除垃圾回收器

  • Abraham Marín Pérez
  • 金灵杰

2017 年 3 月 7 日

话题:JavaJVM语言 & 开发架构

来自 RedHat 的性能和 OpenJDK 开发者Aleksey Shipilëv,提交了一份新的JEP 草案,其内容为创建一个无操作垃圾回收器:一种实际上不进行实际内存回收的 GC 方式。该回收器旨在帮助 JVM 实现者和研究者,以及少部分无需垃圾回收的超高性能应用程序。如果这项 JEP 继续推进,新的 GC 方式将会和现有 GC 方式一起存在,并且通过显式激活方式使用。

垃圾回收和 Java 性能向来都是复杂的话题,为了能够更清晰的说明,InfoQ 联系了 Java Champions 成员、性能专家 Martijn Verburg 和 Kirk Pepperdine。同时,我们也联系了无 GC Log4j转型领导者 Remko Popma,作为如何实现这一 GC 方式的代表人物之一。Martijn 和 Remko 确认,在他们看来,无操作 GC——既 Epsilon GC 的首要受益者是 GC 的开发者和性能研究人员。Epsilon GC 可以作为度量其他垃圾回收器性能的对照组。一个简单的例子,我们可以将一个应用程序的 GC 方式设置成无操作 GC 以减少 GC 开销(以避免内存分配和其他不可控因素)。如果相同应用程序在相同情况下运行,只是修改了不同 GC 算法配置,性能上的不同就可以证明该种 GC 方式在性能上的开销。这将会帮助 GC 开发者和性能研究人员在更加孤立的方式理解垃圾回收行为。

“我认为这实际上是对精确度量 JVM 各个部分跨出的一大步。(例如现存 JIT 的 C1/C2 编译器,可能会被转移到 Graal 等)。它将会为 JVM 增加额外的寿命。”——Martijn Verburg

另一方面,一些对性能严格要求的应用程序也将会从 Epsilon GC 受益。有非常少见的应用程序和库,例如前文提到的 Log4j,已经被设计成不产生垃圾,因此他们无需使用垃圾回收器。对于这种类型应用程序,移除垃圾回收器可以提升性能。然而,如 Remko 强调的,构建一个可以运行在 Epsilon GC 的库,“将花费大量的工程努力,以确保应用程序的内存被自己管理,不会耗尽”。但是这样也必须进行风险和性能评估,以确认选择无操作 GC 的收益和实现无垃圾状态做的努力是否相同。

应用程序如何可以不产生垃圾看上去很难想象,而且这个话题已经复杂到超出本文讨论的范畴,但是如果考虑以下几个方面可能会容易理解:

  • JVM 将内存分成两个部分来管理:堆和栈。这就是为什么当缺少内存时会有两个不同的错误(OutOfMemoryError 和 StackOverflowError)。栈内存只能被当前线程和当前执行的方法访问,因此,当线程离开当前执行的方法,这块内存会被自动释放,而无需额外垃圾回收器。然而,堆内存可以被整个应用程序在任何时刻访问,这意味着独立的垃圾回收器需要区分内存块什么时候才不被使用,可以被回收。
  • 基本数据类型内存总是在栈上分配,因此这对垃圾回收器没有压力。如果代码中基本上都使用基本类型,那么垃圾回收器处理的对象就少了。
  • 不产生垃圾不等于不创建对象,如果对象创建满足以下几个条件,仍然可以在创建对象之后不需要垃圾回收器:
    • 应用程序或者库在初始化的时候生成有限个数的对象,然后不断复用这些对象。但是这需要依赖开发者非常熟悉应用程序的内存占用。
    • 有的时候编译器可以发现一些特定对象不会在方法外使用,这被称为逃逸分析。当确认对象生命周期不会超过方法,其内存可以分配到栈而非堆。因此,这些对象占用的内存会在当前方法结束的时候自动消除。

虽然去除垃圾回收可能实现,Kirk 指出这样编写代码会非常不自然,并且会失去 Java 提供的很多特性。Martijn 也同时指出,内存管理恰恰也是 Java 能够在业界成功的一个主要原因。另外,我们需要牢记,虽然被叫做垃圾回收器,垃圾回收器的任务不仅仅是回收无用的内存,还包括分配新的内存块,Epsilon GC 仍然需要实现该功能。这也正是 Kirk 的论据,至少理论上,使用 Epsilon GC 和将其他垃圾回收算法配置成实际不进行垃圾回收之后,在性能上没有显著差异。

然而,尽管考虑到所有这些注意事项,Kirk 和 Martijn 证实 Epsilon GC 还是会对一小部分受众非常有用,但是根据 Kirk 所说,这些受众将非常少,并怀疑它的实际用处。绝大部分应用程序需要在一些时刻回收内存,因此需要一个有实际功能的垃圾回收器。

“合理的 GC 停顿时间对于大部分应用程序来说不是问题,因此为什么要为了一个可能存疑的性能优势而放弃 Java 的所有好处?”——Kirk Pepperdine

Kirk 同时提到,新增的每一项新特性都会增加 OpenJDK 的维护成本,因此 OpenJDK 开发人员在添加新特性时必须有全局考虑。Oracle 一直在减少垃圾回收器的数量,以降低维护成本,因此为一小部分用户添加一个垃圾回收器可能不是一个合理的投资。Aleksey 在起草 JEP 草案的时候,看上去考虑到了这些点,通过 JEP 草案内容和已经提供的原型来看,初步分析开销可能不大。事实上,Kirk 和 Martijn 同时指出,考虑到 Aleksey 的经验,应该对他所领导的这个倡议保持乐观。

另一方面,Kirk 也强调,虽然 OpenJDK 是 JVM 的一个参考实现,但是对垃圾回收器没有强制性要求,这意味着只要完全兼容 Java 规范,分发者可以有他们自己的垃圾回收算法实现。这可能导致公众意见的分歧:其中一部分人可能认为为特定市场实现的算法可能更适合商业版本的 JVM,另一些人可能会认为这对 OpenJDK 是一个非常有用的补充。

Remko 还建议当性能变得至关重要时,应该考虑使用商业版 JVM,因为在购买许可证上的开销会比让工程师选择和调优特定的 GC 算法要小。但是,即使有些人只选择使用 OpenJDK,Remko 和 Martijn 都提到当前正在开发中的Shenandoah GC,其目的是针对非常大的堆内存(100GB 甚至更大)也只有超低停顿时间。无论如何,专家们的共识是,当应用程序遇到性能问题时,一个经过谨慎选择的 GC 算法总是优于完全没有 GC。

该提案仍然处于初级阶段,在称为正式 JEP 之前仍需要进行审查和润色。让它称为正式稿之后,将会被加入到 JVM 版本中。虽然目前我们只能推测最终会添加到什么版本 JVM 中,Martijn 认为有理由期待 Epsilon GC 将会加入到 Java 10 或者 11。如果要说有什么好处的话,Epsilon GC 至少能够帮助理解 GC 的接口,有助于成就一个更加模块化的 JVM

查看英文原文:The Last Frontier in Java Performance: Remove the Garbage Collector

JavaJVM语言 & 开发架构