跳到主要内容

JEP 416:使用方法句柄重新实现核心反射

概括

在方法句柄之上重新实现java.lang.reflect.MethodConstructor、 和。让方法处理反射的底层机制将减少 API 的维护和开发成本。Field``java.lang.invoke``java.lang.reflect``java.lang.invoke

非目标

对 API 进行任何更改并不是目标java.lang.reflect。这只是一个实施更改。

动机

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

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

随着java.lang.invokeJava 7中引入的方法句柄API,反射操作共有三种不同的内部机制:

  • VM本机方法,

  • 动态生成 和 的字节码存根Method::invokeConstructor::newInstance以及 和的Unsafe字段访问,和Field::get``set

  • 方法句柄。

当我们更新java.lang.reflectjava.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受益。

为了获得最佳性能,MethodConstructorField实例应保存在static final字段中,以便 JIT 可以不断折叠它们。完成后,微基准测试显示新实现的性能明显比旧实现快 43-57%。

MethodConstructor、 和Field实例保存在非常量字段中(例如,在非字段final或数组元素中)时,微基准测试会显示出一些性能下降。当Field实例无法常量折叠时,字段访问的性能明显比旧实现慢 51-77% 。

然而,这种降级可能不会对实际应用程序的性能产生太大影响。我们使用真实世界的库运行了几个序列化和反序列化基准测试,发现没有退化

我们将继续探索提高性能的机会,例如通过改进字段访问的字节码形状,使JIT 能够可靠地优化具体的MethodHandles 和s,无论接收器是否恒定。VarHandle

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

备择方案

替代方案 1:什么也不做

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

我们拒绝这种替代方案,因为

  • java.lang.reflect更新和java.lang.invoke支持 Valhalla 项目的原始类和通用专业化的成本将会很高,

  • 虚拟机中可能需要额外的特殊规则来支持旧类文件格式限制内的新语言功能,并且

  • Loom 项目需要找到一种方法来处理通过核心反射引入的本机堆栈帧。

替代方案 2:升级到新的字节码库

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

这种替代方案的兼容性风险比我们上面提出的要低,但它仍然是相当大的工作量,并且它仍然具有第一个替代方案的第一个和最后一个缺点。

测试

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

基线

Benchmark                                     Mode  Cnt   Score  Error  Units
ReflectionSpeedBenchmark.constructorConst avgt 10 68.049 ± 0.872 ns/op
ReflectionSpeedBenchmark.constructorPoly avgt 10 94.132 ± 1.805 ns/op
ReflectionSpeedBenchmark.constructorVar avgt 10 64.543 ± 0.799 ns/op
ReflectionSpeedBenchmark.instanceFieldConst avgt 10 35.361 ± 0.492 ns/op
ReflectionSpeedBenchmark.instanceFieldPoly avgt 10 67.089 ± 3.288 ns/op
ReflectionSpeedBenchmark.instanceFieldVar avgt 10 35.745 ± 0.554 ns/op
ReflectionSpeedBenchmark.instanceMethodConst avgt 10 77.925 ± 2.026 ns/op
ReflectionSpeedBenchmark.instanceMethodPoly avgt 10 96.094 ± 2.269 ns/op
ReflectionSpeedBenchmark.instanceMethodVar avgt 10 80.002 ± 4.267 ns/op
ReflectionSpeedBenchmark.staticFieldConst avgt 10 33.442 ± 2.659 ns/op
ReflectionSpeedBenchmark.staticFieldPoly avgt 10 51.918 ± 1.522 ns/op
ReflectionSpeedBenchmark.staticFieldVar avgt 10 33.967 ± 0.451 ns/op
ReflectionSpeedBenchmark.staticMethodConst avgt 10 75.380 ± 1.660 ns/op
ReflectionSpeedBenchmark.staticMethodPoly avgt 10 93.553 ± 1.037 ns/op
ReflectionSpeedBenchmark.staticMethodVar avgt 10 76.728 ± 1.614 ns/op

新实施

Benchmark                                     Mode  Cnt    Score   Error  Units
ReflectionSpeedBenchmark.constructorConst avgt 10 32.392 ± 0.473 ns/op
ReflectionSpeedBenchmark.constructorPoly avgt 10 113.947 ± 1.205 ns/op
ReflectionSpeedBenchmark.constructorVar avgt 10 76.885 ± 1.128 ns/op
ReflectionSpeedBenchmark.instanceFieldConst avgt 10 18.569 ± 0.161 ns/op
ReflectionSpeedBenchmark.instanceFieldPoly avgt 10 98.671 ± 2.015 ns/op
ReflectionSpeedBenchmark.instanceFieldVar avgt 10 54.193 ± 3.510 ns/op
ReflectionSpeedBenchmark.instanceMethodConst avgt 10 33.421 ± 0.406 ns/op
ReflectionSpeedBenchmark.instanceMethodPoly avgt 10 109.129 ± 1.959 ns/op
ReflectionSpeedBenchmark.instanceMethodVar avgt 10 90.420 ± 2.187 ns/op
ReflectionSpeedBenchmark.staticFieldConst avgt 10 19.080 ± 0.179 ns/op
ReflectionSpeedBenchmark.staticFieldPoly avgt 10 92.130 ± 2.729 ns/op
ReflectionSpeedBenchmark.staticFieldVar avgt 10 53.899 ± 1.051 ns/op
ReflectionSpeedBenchmark.staticMethodConst avgt 10 35.907 ± 0.456 ns/op
ReflectionSpeedBenchmark.staticMethodPoly avgt 10 102.895 ± 1.604 ns/op
ReflectionSpeedBenchmark.staticMethodVar avgt 10 82.123 ± 0.629 ns/op

风险和假设

依赖于高度特定于实现且现有实现未记录的方面的代码可能会受到影响。为了减轻这种兼容性风险,作为解决方法,您可以通过启用旧的实现-Djdk.reflect.useDirectMethodHandle=false

  • 检查内部生成的反射类(即 的子类MagicAccessorImpl)的代码将不再工作,必须更新。

  • 方法句柄调用可能比旧的核心反射实现消耗更多的资源。这种调用涉及调用多个Java方法以确保成员的声明类在访问之前被初始化,因此可能需要更多的堆栈空间来用于必要的执行帧。这可能会导致一个StackOverflowError或,如果StackOverflowError在初始化类时抛出一个,则抛出一个NoClassDefFoundError

  • 我们将在未来的版本中删除旧的核心反射实现。-Djdk.reflect.useDirectMethodHandle=false此时解决方法将停止工作。