Skip to content

JEP 416: Reimplement Core Reflection with Method Handles | 使用方法句柄重新实现核心反射

摘要

java.lang.invoke 方法句柄的基础上重新实现 java.lang.reflect.MethodConstructorField。将方法句柄作为反射的底层机制将降低 java.lang.reflectjava.lang.invoke API 的维护和开发成本。

非目标

不对 java.lang.reflect API 进行任何更改。这仅是一项实现更改。

动机

核心反射具有调用方法和构造函数的两种内部机制。为了快速启动,它在 HotSpot VM 中使用本地方法,针对特定反射方法或构造函数对象的最初几次调用。为了获得更好的峰值性能,在多次调用后,它会为反射操作生成字节码,并在后续调用中使用该字节码。

对于字段访问,核心反射使用内部 sun.misc.Unsafe API。

随着 Java 7 中引入的 java.lang.invoke 方法句柄 API,现在总共有三种不同的内部机制用于反射操作:

  • VM 本地方法,

  • Method::invokeConstructor::newInstance 动态生成的字节码存根,以及使用 Unsafe 进行字段访问的 Field::getset

  • 方法句柄。

当我们更新 java.lang.reflectjava.lang.invoke 以支持新的语言特性(如 Valhalla 项目 中所设想的)时,我们必须修改所有这三个代码路径,这成本高昂。此外,当前实现依赖于 VM 对生成字节码的特殊处理,这些字节码被封装在 jdk.internal.reflect.MagicAccessorImpl 的子类中:

  • 放宽可访问性,以便这些类可以访问其他类的不可访问字段和方法,

  • 禁用验证以绕过 JLS §6.6.2,以支持对 Object::clone 的反射,

  • 使用非良好行为的类加载器来解决一些安全和兼容性问题。

描述

通过在方法句柄的基础上重新实现 java.lang.reflect,将其作为平台通用的底层反射机制,替换 Method::invokeConstructor::newInstanceField::getField::set 的字节码生成实现。

新实现直接对特定反射对象的方法句柄进行调用。我们仅在 VM 启动初期,即方法句柄机制初始化之前,使用 VM 的本地反射机制。这发生在 System::initPhase1 之后和 System::initPhase2 之前,之后我们将完全切换到使用方法句柄。这通过减少本地栈帧的使用,对 Project Loom 项目有益。

为了获得最佳性能,应将 MethodConstructorField 实例保存在 static final 字段中,以便 JIT 编译器可以进行常量折叠。当这样做时,微基准测试显示新实现的性能比旧实现显著提高,提升幅度为 43-57%。

MethodConstructorField 实例保存在非常量字段中(例如,在非 final 字段或数组元素中)时,微基准测试显示性能有所下降。当 Field 实例无法进行常量折叠时,字段访问的性能比旧实现慢 51-77%。

然而,这种性能下降可能对实际应用程序的性能影响不大。我们使用实际库运行了几个序列化和反序列化基准测试,并未发现以下情况中的性能下降:

我们将继续探索提高性能的机会,例如通过优化字段访问的字节码形状,使无论接收者是否为常量,具体的 MethodHandleVarHandle 都能被 JIT 编译器可靠地优化。

新实现将降低为新语言特性升级反射支持的成本,并进一步允许我们通过移除对 MagicAccessorImpl 子类的特殊处理来简化 HotSpot VM。

备选方案

备选方案 1:不采取任何行动

保留现有的核心反射实现,以避免任何兼容性风险。为核心反射生成的动态字节码将保持在类文件版本 49,VM 将继续特别处理此类字节码。

我们拒绝此备选方案,因为

  • 更新 java.lang.reflectjava.lang.invoke 以支持 Project Valhalla 的原始类和泛型特化将成本高昂,

  • 在旧类文件格式的限制下,VM 中可能需要额外的特殊规则来支持新语言特性,

  • Project Loom 需要找到一种方法来处理核心反射引入的本地栈帧。

方案二:升级到新的字节码库

将核心反射所使用的字节码写入器替换为与类文件格式一同发展的新字节码库,但保留现有的核心反射实现,并继续对动态生成的反射字节码进行特殊处理。

此方案相比我们之前提出的方案具有更低的兼容性风险,但仍然是大量工作,且仍具有方案一的第一和最后一个缺点。

测试

全面的测试将确保实现的健壮性和与现有行为的兼容性。性能测试将确保与当前实现相比,不会出现意外的显著性能下降。我们将鼓励使用早期访问版本的开发者尽可能多地测试库和框架,以帮助我们识别任何行为或性能回归。