JEP 416: Reimplement Core Reflection with Method Handles | 使用方法句柄重新实现核心反射
摘要
在 java.lang.invoke
方法句柄的基础上重新实现 java.lang.reflect.Method
、Constructor
和 Field
。将方法句柄作为反射的底层机制将降低 java.lang.reflect
和 java.lang.invoke
API 的维护和开发成本。
非目标
不对 java.lang.reflect
API 进行任何更改。这仅是一项实现更改。
动机
核心反射具有调用方法和构造函数的两种内部机制。为了快速启动,它在 HotSpot VM 中使用本地方法,针对特定反射方法或构造函数对象的最初几次调用。为了获得更好的峰值性能,在多次调用后,它会为反射操作生成字节码,并在后续调用中使用该字节码。
对于字段访问,核心反射使用内部 sun.misc.Unsafe
API。
随着 Java 7 中引入的 java.lang.invoke
方法句柄 API,现在总共有三种不同的内部机制用于反射操作:
VM 本地方法,
为
Method::invoke
和Constructor::newInstance
动态生成的字节码存根,以及使用Unsafe
进行字段访问的Field::get
和set
,方法句柄。
当我们更新 java.lang.reflect
和 java.lang.invoke
以支持新的语言特性(如 Valhalla 项目 中所设想的)时,我们必须修改所有这三个代码路径,这成本高昂。此外,当前实现依赖于 VM 对生成字节码的特殊处理,这些字节码被封装在 jdk.internal.reflect.MagicAccessorImpl
的子类中:
放宽可访问性,以便这些类可以访问其他类的不可访问字段和方法,
禁用验证以绕过 JLS §6.6.2,以支持对
Object::clone
的反射,使用非良好行为的类加载器来解决一些安全和兼容性问题。
描述
通过在方法句柄的基础上重新实现 java.lang.reflect
,将其作为平台通用的底层反射机制,替换 Method::invoke
、Constructor::newInstance
、Field::get
和 Field::set
的字节码生成实现。
新实现直接对特定反射对象的方法句柄进行调用。我们仅在 VM 启动初期,即方法句柄机制初始化之前,使用 VM 的本地反射机制。这发生在 System::initPhase1
之后和 System::initPhase2
之前,之后我们将完全切换到使用方法句柄。这通过减少本地栈帧的使用,对 Project Loom 项目有益。
为了获得最佳性能,应将 Method
、Constructor
和 Field
实例保存在 static final
字段中,以便 JIT 编译器可以进行常量折叠。当这样做时,微基准测试显示新实现的性能比旧实现显著提高,提升幅度为 43-57%。
当 Method
、Constructor
和 Field
实例保存在非常量字段中(例如,在非 final
字段或数组元素中)时,微基准测试显示性能有所下降。当 Field
实例无法进行常量折叠时,字段访问的性能比旧实现慢 51-77%。
然而,这种性能下降可能对实际应用程序的性能影响不大。我们使用实际库运行了几个序列化和反序列化基准测试,并未发现以下情况中的性能下降:
- 使用 Jackson 的自定义 JSON 序列化和反序列化基准测试,
- XStream 转换器类型基准测试,
- Kryo 字段序列化器基准测试。
我们将继续探索提高性能的机会,例如通过优化字段访问的字节码形状,使无论接收者是否为常量,具体的 MethodHandle
和 VarHandle
都能被 JIT 编译器可靠地优化。
新实现将降低为新语言特性升级反射支持的成本,并进一步允许我们通过移除对 MagicAccessorImpl
子类的特殊处理来简化 HotSpot VM。
备选方案
备选方案 1:不采取任何行动
保留现有的核心反射实现,以避免任何兼容性风险。为核心反射生成的动态字节码将保持在类文件版本 49,VM 将继续特别处理此类字节码。
我们拒绝此备选方案,因为
更新
java.lang.reflect
和java.lang.invoke
以支持 Project Valhalla 的原始类和泛型特化将成本高昂,在旧类文件格式的限制下,VM 中可能需要额外的特殊规则来支持新语言特性,
Project Loom 需要找到一种方法来处理核心反射引入的本地栈帧。
方案二:升级到新的字节码库
将核心反射所使用的字节码写入器替换为与类文件格式一同发展的新字节码库,但保留现有的核心反射实现,并继续对动态生成的反射字节码进行特殊处理。
此方案相比我们之前提出的方案具有更低的兼容性风险,但仍然是大量工作,且仍具有方案一的第一和最后一个缺点。
测试
全面的测试将确保实现的健壮性和与现有行为的兼容性。性能测试将确保与当前实现相比,不会出现意外的显著性能下降。我们将鼓励使用早期访问版本的开发者尽可能多地测试库和框架,以帮助我们识别任何行为或性能回归。