跳到主要内容

JEP 122:移除永久代

QWen Max 中英对照

概述

移除 Hotspot JVM 的永久代,从而无需调整永久代的大小。

非目标

扩展类数据共享至应用类。减少类元数据所需的内存。启用类元数据的异步收集。

成功指标

类元数据、驻留字符串和类静态变量将从永久代移至 Java 堆或本地内存。

Hotspot JVM 中的永久代代码将被删除。

应用启动和内存占用的性能指标,根据尚未选定的一组基准测试衡量,不会退步超过 1%。

动机

这是 JRockit 和 Hotspot 融合工作的一部分。JRockit 用户不需要配置永久代(因为 JRockit 没有永久代),并且已经习惯了不配置永久代。

描述

将 Hotspot 中永久代的部分内容移动到 Java 堆中,剩下的部分移动到本地内存中。

Hotspot 的 Java 类表示(这里称为类元数据)当前存储在 Java 堆的一部分中,这部分被称为永久代。此外,内部化的字符串和类静态变量也存储在永久代中。永久代由 Hotspot 管理,必须有足够的空间容纳 Java 应用程序使用的所有类元数据、内部化字符串和类静态变量。当类被加载时,类元数据和静态变量会在永久代中分配,并在类被卸载时从永久代中进行垃圾回收。当永久代进行垃圾回收时,内部化的字符串也会被垃圾回收。

所提议的实现方案将会在本地内存中分配类元数据,并将驻留字符串(interned Strings)和类静态变量(class statics)移动到 Java 堆中。Hotspot 将会显式地为类元数据分配和释放本地内存。新类元数据的分配将受到可用本地内存量的限制,而不是由 -XX:MaxPermSize 的值所固定,无论该值是默认的还是通过命令行指定的。

类元数据的本地内存分配将以足够大的块大小进行,以容纳多个类元数据片段。每个块将与一个类加载器相关联,并且该类加载器加载的所有类元数据都将由 Hotspot 从该类加载器对应的块中分配。根据需要,将为类加载器分配额外的块。块的大小将根据应用程序的行为而有所不同。大小的选择将旨在限制内部和外部碎片。当类加载器终止时,通过释放与该类加载器关联的所有块来释放类元数据的空间。在类的生命周期内,类元数据不会被移动。

替代方案

通过拥有一个可以增长的永久代,可以实现消除对永久代进行大小调整的需求这一目标。还有一些其他的数据结构需要随着永久代一起增长(例如卡表和块偏移表)。为了实现高效的实施,永久代需要看起来像一个连续的空间,其中某些部分不可用。

测试

在测试期间,需要监控原生内存使用情况的变化,以查找内存泄漏。

风险与假设

Hotspot JVM 的更改范围是主要风险。此外,确定需要更改的确切内容很可能只能在实施过程中确定。

这是一个大型项目,广泛影响所有的垃圾收集器。永久代的相关知识及其工作原理存在于 Hotspot JVM 的运行时和编译器部分中。垃圾收集器外部的数据结构将会进行更改,以促进垃圾收集器在本地内存中对类元数据的处理。

作为该项目的一部分,JVM 的某些部分可能需要重新实现。例如,类数据共享将受到影响,可能需要全部或部分重新实现。

类重定义是一个存在风险的领域。重定义依赖于在永久代收集过程中对类元数据的垃圾回收(即,重定义目前不会释放已被重定义的类,因此需要某种方法来确定何时可以释放已重定义类的元数据)。

将驻留字符串(interned Strings)和类静态变量(class statics)移动到 Java 堆可能会导致内存不足(Out-of-memory)异常或垃圾回收(GC)次数增加。用户可能需要调整 -Xmx 参数。

使用 UseCompressedOops 选项时,指向类元数据(在永久代中)的指针可以像指向 Java 堆的指针一样被压缩。这带来了显著的性能提升(大约几个百分点)。指向本地内存中元数据的指针也会以类似的方式进行压缩,但实现方式有所不同。后者的实现可能不如压缩指向 Java 堆的指针那样高性能。压缩指向元数据的指针的需求可能会对元数据的大小施加一个上限。例如,如果实现要求所有元数据都分配在某个地址之下(例如低于 4 GB 的限制),那么这将限制元数据的大小。

依赖

了解永久代的工具将需要重新实现。服务代理、jconsole、Java VisualVM 和 jhat 是将受影响的工具的例子。

影响

  • 其他 JDK 组件:具有永久代知识的工具。
  • 兼容性:与永久代相关的命令行标志将变得过时。
  • 文档:需要删除对永久代的引用。