跳到主要内容

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

QWen Max 中英对照 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 虚拟机中使用本地方法来处理特定反射方法或构造函数对象的前几次调用。为了获得更好的峰值性能,在多次调用后,它会为反射操作生成字节码,并在后续调用中使用该字节码。

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

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

  • VM 原生方法,

  • Method::invokeConstructor::newInstance 动态生成的字节码存根,以及用于 Field::getsetUnsafe 字段访问,还有

  • 方法句柄。

当我们更新 java.lang.reflectjava.lang.invoke 以支持新的语言特性时,例如 Project Valhalla 中设想的那些特性,我们必须修改所有三条代码路径,这会带来很高的成本。此外,当前的实现依赖于虚拟机对生成字节码的特殊处理,这些字节码被封装在 jdk.internal.reflect.MagicAccessorImpl 的子类中:

  • 放宽了可访问性限制,以便这些类可以访问其他类中无法访问的字段和方法,

  • 为规避 JLS §6.6.2 而禁用了验证,从而支持对 Object::clone 的反射操作,并且

  • 使用了一个行为不规范的类加载器来规避一些安全性和兼容性问题。

描述

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

新的实现对特定反射对象的方法句柄进行直接调用。我们仅在虚拟机启动早期、方法句柄机制初始化之前,使用虚拟机的原生反射机制。这一过程发生在 System::initPhase1 之后不久,并在 System::initPhase2 之前完成,此后我们将完全切换为使用方法句柄。这通过减少原生栈帧的使用,为 Project Loom 带来了性能提升。

为了获得最佳性能,MethodConstructorField 实例应保存在 static final 字段中,以便 JIT 可以对其进行常量折叠。完成此操作后,微基准测试表明新实现的性能比旧实现显著提高了 43% 到 57%。

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

然而,这种退化可能对现实世界应用程序的性能没有太大影响。我们使用真实世界的库运行了多个序列化和反序列化基准测试,发现并没有出现性能下降的情况。

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

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

替代方案

替代方案 1:什么都不做

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

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

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

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

  • Project 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 的解决方法将不再有效。