JEP 416: Reimplement Core Reflection with Method Handles

OwnerMandy Chung
TypeFeature
ScopeJDK
StatusCandidate
Componentcore-libs / java.lang:reflect
Discussioncore dash libs dash dev at openjdk dot java dot net
EffortM
DurationM
Reviewed byAlan Bateman, John Rose
Endorsed byJohn Rose
Created2021/04/26 22:41
Updated2021/08/07 20:12
Issue8266010

Summary

Reimplement java.lang.reflect.Method, Constructor, and Field on top of java.lang.invoke method handles. Making method handles the underlying mechanism for reflection will reduce the maintenance and development cost of both the java.lang.reflect and java.lang.invoke APIs.

Non-Goals

It is not a goal to make any change to the java.lang.reflect API. This is solely an implementation change.

Motivation

Core reflection has two internal mechanisms for invoking methods and constructors. For fast startup, it uses native methods in the HotSpot VM for the first few invocations of a specific reflective method or constructor object. For better peak performance, after a number of invocations it generates bytecode for the reflective operation and uses that in subsequent invocations.

For field access, core reflection uses the internal sun.misc.Unsafe API.

With the java.lang.invoke method-handle API introduced in Java 7, there are altogether three different internal mechanisms for reflective operations:

When we update java.lang.reflect and java.lang.invoke to support new language features, such as those envisioned in Project Valhalla, we must modify all three code paths, which is costly. In addition, the current implementation relies on special treatment by the VM of the generated bytecode, which is wrapped in subclasses of jdk.internal.reflect.MagicAccessorImpl:

Description

Reimplement java.lang.reflect on top of method handles as the common underlying reflective mechanism of the platform by replacing the bytecode-generating implementations of Method::invoke, Constructor::newInstance, Field::get, and Field::set.

For the first few invocations of one of these reflective methods on a specific reflective object we invoke the corresponding method handle directly. After that we spin a dynamic bytecode stub defined in a hidden class which loads the target MethodHandle from its class data as a dynamically computed constant. Loading the method handle from a constant allows the HotSpot VM to inline the method-handle invocation in order to achieve good performance.

The VM's native reflection methods are still needed during early startup, before the method-handle mechanism is initialized. That happens soon after System::initPhase1 and before System::initPhase2, after which we switch to using method handles exclusively. This benefits Project Loom by reducing the use of native stack frames.

Microbenchmarks show that the performance of the new implementation of Method::invoke, Field::get and Field::set on instance members is faster than the old implementation. The performance of Field::get on static fields is comparable to the old implementation. The performance of Field::set on static fields and Constructor::newInstance is slightly slower. The cold startup time of a simple application that uses Method::invoke on 32 methods increased from 64ms to 70ms. We will continue to investigate ways to address these minor regressions.

This approach will reduce the cost of upgrading reflection support for new language features and, further, allow us to simplify the HotSpot VM by removing the special treatment of MagicAccessorImpl subclasses.

Alternatives

Alternative 1: Do nothing

Retain the existing core reflection implementation to avoid any compatibility risk. The dynamic bytecode generated for core reflection would remain at class file version 49, and the VM would continue to treat such bytecode specially.

We reject this alternative because

Alternative 2: Upgrade to a new bytecode library

Replace the bytecode writer used by core reflection to use a new bytecode library that evolves together with the class-file format, but otherwise retain the existing core reflection implementation and continue to treat dynamically-generated reflection bytecode specially.

This alternative has lower compatibility risk than what we propose above, but it is still a sizeable amount of work and it still has the first and last disadvantages of the first alternative.

Testing

Comprehensive testing will ensure that the implementation is robust and compatible with existing behavior. Performance testing will ensure that there are no significant performance regressions compared to the current implementation. We will encourage developers using early-access builds to test as many libraries and frameworks as possible in order to help us identify any behavior or performance regressions.

Microbenchmarks show no significant performance regressions as well as improvements in many cases. We continue to explore opportunities to improve performance.

Baseline

Benchmark                                           Mode  Cnt    Score    Error  Units
ReflectionFields.getInt_instance_field              avgt   10    8.058 ±  0.003  ns/op
ReflectionFields.getInt_instance_field_var          avgt   10    7.576 ±  0.097  ns/op
ReflectionFields.getInt_static_field                avgt   10    5.937 ±  0.002  ns/ops
ReflectionFields.getInt_static_field_var            avgt   10    6.810 ±  0.027  ns/ops
ReflectionFields.setInt_instance_field              avgt   10    5.102 ±  0.023  ns/ops
ReflectionFields.setInt_instance_field_var          avgt   10    5.139 ±  0.006  ns/ops
ReflectionFields.setInt_static_field                avgt   10    4.245 ±  0.002  ns/ops
ReflectionFields.setInt_static_field_var            avgt   10    3.920 ±  0.003  ns/ops
ReflectionMethods.class_forName_1arg                avgt   10  407.448 ±  0.823  ns/ops
ReflectionMethods.class_forName_1arg_var            avgt   10  418.611 ±  8.790  ns/ops
ReflectionMethods.class_forName_3arg                avgt   10  366.685 ±  5.713  ns/ops
ReflectionMethods.class_forName_3arg_var            avgt   10  359.410 ±  3.926  ns/ops
ReflectionMethods.instance_method                   avgt   10   17.428 ±  0.020  ns/ops
ReflectionMethods.instance_method_var               avgt   10   20.249 ±  0.065  ns/ops
ReflectionMethods.static_method                     avgt   10   18.843 ±  0.035  ns/ops
ReflectionMethods.static_method_var                 avgt   10   19.460 ±  0.050  ns/ops

New implementation

Benchmark                                           Mode  Cnt     Score     Error  Units
ReflectionFields.getInt_instance_field              avgt   10     6.361 ±   0.002  ns/op
ReflectionFields.getInt_instance_field_var          avgt   10     5.976 ±   0.112  ns/op
ReflectionFields.getInt_static_field                avgt   10     5.946 ±   0.003  ns/op
ReflectionFields.getInt_static_field_var            avgt   10     6.372 ±   0.014  ns/op
ReflectionFields.setInt_instance_field              avgt   10     4.672 ±   0.013  ns/op
ReflectionFields.setInt_instance_field_var          avgt   10     3.933 ±   0.009  ns/op
ReflectionFields.setInt_static_field                avgt   10     4.661 ±   0.001  ns/op
ReflectionFields.setInt_static_field_var            avgt   10     3.953 ±   0.014  ns/op
ReflectionMethods.class_forName_1arg                avgt   10   404.300 ±   1.423  ns/op
ReflectionMethods.class_forName_1arg_var            avgt   10   402.458 ±   0.418  ns/op
ReflectionMethods.class_forName_3arg                avgt   10   394.287 ±   3.443  ns/op
ReflectionMethods.class_forName_3arg_var            avgt   10   377.586 ±   0.270  ns/op
ReflectionMethods.instance_method                   avgt   10    13.645 ±   0.019  ns/op
ReflectionMethods.instance_method_var               avgt   10    13.811 ±   0.029  ns/op
ReflectionMethods.static_method                     avgt   10    13.723 ±   0.026  ns/op
ReflectionMethods.static_method_var                 avgt   10    13.164 ±   0.046  ns/op

Risks and Assumptions

Code that depends upon highly implementation-specific and undocumented aspects of the existing implementation may be impacted. To mitigate this compatibility risk, as a workaround you can enable the old implementation via -Djdk.reflect.useDirectMethodHandle=false.