JEP Draft: Override reflections with method handles

Reimplement Core Reflection with Method Handles (Java.net)

Translator: Method handler: Method handler

Owner Mandy Chung
Type Feature
Scope JDK
Status Submitted
Component The core – libs/Java. Lang: reflect
Effort M
Duration M
Reviewed by Alan Bateman, John Rose
Endorsed by John Rose
Created 2021/04/26 22:41
Updated 2021/07/28″
Issue 8266010

The total is

In Java. Lang. Invoke method based on the handlers to implement Java lang. Reflect. Method, Constructor, and the Field. Making method handles reflect the underlying mechanism reduces the maintenance and development costs of the Java.lang. Reflect and java.lang.Invokeapi

The target

No changes will be made to the Java.lang.Reflect API. This is just a modification to its underlying implementation.

motivation

There are two internal mechanisms for reflection of calling methods and constructors. For quick startup, the Hotspot VIRTUAL machine’s native method is used for the first few calls to specific reflection methods and constructors, and for better performance, after a few calls, it generates bytecode for reflection operations and uses it in subsequent calls

For field access, reflection uses the internal Sun.misc.unsafeAPI

Through the java.lang. Invoke method handle API introduced in Java 7, there are three different internal reflection operation mechanisms:

  • Native method of virtual machine
  • forMethod::invokeandConstructor::newInstanceDynamically generated bytecode, as well as theField::getset UnsafeThe field access mechanism of
  • Method handles

When we update java.lang.Reflect and java.lang.invoke to support new language features, such as those anticipated in Project Valhalla, which may require us to modify all three code paths, these changes will be costly. In addition, the existing implementation depends on the virtual machine to generate bytecode special processing, it is wrapped in the JDK. Internal. Reflect. MagicAccessorImpl subclasses:

Special processing of generated bytecode by the virtual machine

  • Ease accessibility so that these classes can access inaccessible fields and methods of other classes
  • Verification is turned off for resolutionJLS § 6.6.2To support theObject::cloneThe reflection of
  • A poorly performing class loader will be used to address security and compatibility issues

describe

The java.lang.Reflect reimplementation based on method handles will serve as the platform’s common reflection underlying implementation mechanism, To replace bytecode generation mechanism based Method: : invoke, Constructor: : newInstance, Field: : get, and the Field: : the set

For the first few calls to one of these reflection methods on a particular reflection object, we call the corresponding method handle directly. After we will rotate (spun) defined in the class [hidden] (docs.oracle.com/en/java/jav… . Lookup.html#defineHiddenClassWithClassData(byte[],java.lang.Object,boolean,java.lang.invoke.MethodHandles.Lookup.ClassOp tion…) Bytecode), it will be from [data] (docs.oracle.com/en/java/jav… .string, java.lang.class) loads the target MethodHandle as a dynamically computed constant. Loading method handles from constants allows HotSpot VM to inline method handle calls for good performance.

Before the method handle mechanism is initialized, the virtual machine’s native reflection method is still required during the early startup phase. This will happen after System::initPhase1 and before System::initPhase2, after which we will switch to using method handles only. Project Loom benefits by reducing local method stack frames

Microbenchmarking shows that the new implementations of Method:: Invoke, Field:: Get, and Field:: Set on instance members perform faster than the old implementations. Field:: GET performs as well on static fields as the older implementation. Field::set performs slightly slower on static fields and Constructor::newInstance. The cold start time of a simple application using Method:: Invoke on 32 methods was increased from 64 milliseconds to 70 milliseconds. We will continue to work out how to solve these small problems.

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

Caller-sensitive methods

Caller-sensitive methods are methods whose behavior depends on the class of their direct caller. The implementation of caller-sensitive methods does a stack walk to find their direct caller, skipping any stack frames introduced by the internal reflection mechanism.

Here are some caller-sensitive methods in the platform:

  • Class::forName(String)Use the class loader of its caller class to load the named class and perform permission checks when security manager is enabled.
  • Method::invoke,Constructor::newInstance,Field::getXField::setXPerform an access check on its caller’s class unless it passessetAccessible(true)Suppress access checks.
  • MethodHandles::lookupReturns using the class of its callerLookupObject’s lookup class.

This sample code shows how to invoke the caller-sensitive Method CSM::returnCallerClass via Method:: Invoke.

class CSM {
    @CallerSensitive staticClass<? > returnCallerClass() {returnReflection.getCallerClass(); }}class Foo {
    void test(a) throws Throwable {
        // calling CSM::returnCallerClass via reflection
        var m =  CSM.class.getMethod("returnCallerClass");
        // expect Foo to be the caller class
        var caller = m.invoke(null);
        assert(caller == Foo.class); }}Copy the code

First, Method:: Invoke finds Foo checked as its direct caller. It checks if Foo has access to CSM::returnCallerClass. It then reflexively calls CSM::returnCallerClass. Since CSM::returnCallerClass is a caller-sensitive method, it will find its direct caller class, skip the reflection stack frame, and return it. In this case, CSM::returnCallerClass finds Foo as the caller class. The stack looks like this:

CSM.returnCallerClass
    jdk.internal.reflect.NativeMethodAccessorImpl.invoke0
    jdk.internal.reflect.NativeMethodAccessorImpl.invoke
    jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke
    java.lang.reflect.Method.invoke
    Foo.test
    :
    :
Copy the code

Notice that the stack traversal to find the caller class is done twice, once for Method:: Invoke and once for CSM::returnCallerClass.

A method handle call to a caller-sensitive method

The general rules of bytecode behavior apply if a request obtains a method handle to a caller-sensitive method, but they look up classes in a special way. The generated method handles behave as if they were called from instructions contained in the Lookup class, so caller-sensitive methods detect the lookup class. (By contrast, the caller to a method handle is ignored.) Therefore, in the case of caller-sensitive methods, different lookup classes may produce method handles that behave differently.

Because of this behavior of caller-sensitive methods, Method:: Invoke, called through a Method handle, does not work properly with calls to target caller-sensitive methods. For example, Bar calls CSM::returnCallerClass via a chain reflection call, as follows:

class Bar {
    void test(a) throws Throwable {
        //Method::invoke Method handle
        MethodHandle mh = MethodHandles.lookup()
            .findVirtual(Method.class, "invoke",
                         methodType(Object.class, Object.class, Object[].class));
        // CSM::returnCallerClass reflection object
        Method m =  CSM.class.getMethod("returnCallerClass");
        // Call Method::invoke with a Method handle and a target function
        // The reflection call is CSM::returnCallerClass
        var caller = mh.invoke(m, null.null);
        assert(caller == Bar.class);           // Fail!}}Copy the code

It is reasonable to expect that a chain reflection call calling CSM::returnCallerClass should behave the same as a static call to CSM::returnCallerClass, that is, Bar should be the returned class. However, the current implementation returns an incorrect caller class.

The stack below shows the internal implementation, including the hidden frame, which reveals the caller class found through stack traversal. Method:: Invoke, on the other hand, is invoked through a Method handle. Method:: Invoke should behave as if it were called by the Lookup class of the Lookup object, creating the specified Method handle, namely Bar.

CSM.returnCallerClass()
    jdk.internal.reflect.NativeMethodAccessorImpl.invoke0
    jdk.internal.reflect.NativeMethodAccessorImpl.invoke
    jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke
    java.lang.reflect.Method.invoke(mh)
    java.lang.invoke.DirectMethodHandle$Holder.invokeSpecial
    java.lang.invoke.LambdaForm$MH/0x0000000800003000.invoke
    java.lang.invoke.LambdaForm$MH/0x0000000800004400.invokeExact_MT
    Bar$$InjectedInvoker/0x0000000800003400.invoke_V             <--- caller
    java.lang.invoke.DirectMethodHandle$Holder.invokeStatic
    java.lang.invoke.LambdaForm$MH/0x0000000800004000.invoke
    java.lang.invoke.LambdaForm$MH/0x0000000800003c00.invoke_MT
    Bar.test 
    :
    :
Copy the code

This example shows the error in the current implementation: two caller-sensitive methods do not work properly if the call is called through chain reflection while relying on stack traversal to find the caller.

Injected a hidden current implementation class Bar $$InjectedInvoker / 0 x0000000800003400, it with the Bar in the same package, and defined by the definition of the same as the Bar loaders, have the same protection domain. Stack traversed will find Bar $$InjectedInvoker / 0 x0000000800003400 as the caller’s class rather than the Bar. This approach works for caller-sensitive methods that depend on a runtime package, a protected domain that defines a loader, or a caller class, but it does not work for MethodHandles:: Lookup calls that require the exact caller class (see 8013527 and 8257874 for details).

A special sequence of calls to caller-sensitive methods

The new implementation introduces a special call sequence for caller-sensitive methods. Caller-sensitive methods can provide a private adapter with the same name, but with an extra Class parameter next to it. When a caller-sensitive method is called through a core reflection or method handle, it looks for the presence of an adapter method with a Class parameter. If found, it invokes the adapter method with the caller class parameter. This particular calling sequence to ensure that the same caller class through Method: : invoke, MethodHandle: : invokeExact or a combination of these methods is passed to the caller sensitive Method.

For example, the CSM::returnCallerClass and its adapter methods would look like this:

class CSM {
    @CallerSensitive staticClass<? > returnCallerClass() {return returnCallerClass(Reflection.getCallerClass());
    }

    private staticClass<? > returnCallerClass(Class<? > caller) {returncaller; }}Copy the code

In the new implementation, the stack for the above example looks like this:

CSM.returnCallerClass(caller)                            <--- adaptor method
    java.lang.invoke.DirectMethodHandle$Holder.invokeStatic
    java.lang.invoke.Invokers$Holder.invokeExact_MT
    jdk.internal.reflect.DirectMethodAccessorImpl$CallerSensitiveWithCaller.invoke
    java.lang.reflect.Method.invoke
    Foo.test
   :
   :
Copy the code

and

CSM.returnCallerClass(caller)                            <--- adaptor method
    java.lang.invoke.DirectMethodHandle$Holder.invokeStatic
    java.lang.invoke.Invokers$Holder.invokeExact_MT
    jdk.internal.reflect.DirectMethodAccessorImpl$CallerSensitiveWithCaller.invoke
    java.lang.reflect.Method.invoke(caller, m)               <--- adaptor method
    java.lang.invoke.DirectMethodHandle$Holder.invokeSpecial
    java.lang.invoke.LambdaForm$MH/0x0000000800004000.invoke
    java.lang.invoke.LambdaForm$MH/0x0000000800003c00.invoke_MT
    Bar.test
    :
    :
Copy the code

Both CSM::returnCallerClass and Method:: Invoke can have an adapter Method that defines the caller class parameters. Foo calls Method:: Invoke, which traverses the stack to find the caller’s class. It passes the caller’s class directly to the adapter method of CSM::returnCallerClass.

Similarly, Bar calls CSM::returnCallerClass by calling Method:: Invoke via a Method handle. In this case, the MethodHandle: : invokeExact generating method is used to handle the Lookup object to find the class as the caller’s class, so do not involve a stack traversal. The lookup class is Bar. It calls the adapter Method of Method:: Invoke with Bar as the caller’s class, which in turn calls the adapter Method of CSM::returnCallerClass with Bar as the caller. The new implementation eliminates the need for multiple stack traversals when reflexively calling caller-sensitive methods.

For caller-sensitive methods that require precise caller classes, adapter methods must be defined to ensure correctness. MethodHandles: : lookup and this: : registerAsParallelCapable is only two in the JDK need exact caller class methods.

On the other hand, adapter methods are optional for caller-sensitive methods that use the caller’s class for access checking or security permission checking, that is, based on their runtime package, definition loader, or protection domain.

The new implementation will use special call sequences to support reflection calls to caller-sensitive methods, with or without adapters.

options

Option 1: Do nothing

Keep the existing core reflection implementation to avoid any compatibility risks. The dynamic bytecodes generated for reflection will remain in class file version 49, and the virtual machine will continue to process such bytecodes specifically.

We reject that option because

  • updatejava.lang.reflectjava.lang.invokeThe cost of supporting Project Valhalla’s primitive classes and generic specialization can be high
  • Additional special rules in the virtual machine may be required to support new language functionality within the limitations of the old class file format, as well
  • Project Loom needs to find a way to handle native stack frames introduced through core reflection.

Option 2: Upgrade to a new bytecode library

Replace the bytecode writer used by reflection to use the new bytecode library that has evolved with the class file format, but retain the existing reflection implementation and continue to deal specifically with dynamically generated reflection bytecode.

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

test

Thorough testing will ensure that the new implementation is robust and compatible with existing behavior. Performance testing will ensure that there is no significant performance regression compared to the current implementation. We would encourage developers to use early access releases to test as many libraries and frameworks as possible to help us identify any behavior or performance regressions.

In many cases, microbenchmarks did not show significant performance regression and improvement. We will continue to explore opportunities to improve performance.

The benchmark

Benchmark Mode Cnt Score Error Units Reflectionfield. getInt_instance_field AVGT 10 8.058 ± 0.003 ns/op GetInt_instance_field_var avgt 10 7.576 ± 0.097 ns/op Reflectionfields. getInt_static_field AVgt 10 GetInt_static_field_var AVgt 10 6.810 ± 0.027 ns/ops SetInt_instance_field AVgt 10 5.102 ± 0.023 ns/ops ReflectionField. setInt_instance_field_var avgt 10 5.139 ± 0.006 ns/ OPS Reflectionfieles. setInt_static_field AVGT 10 4.245 ± 0.002 ns/ OPS 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 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 Static_method_var AVGT 10 19.460 ± 0.050 ns/ OPSCopy the code

The new implementation

Benchmark Mode Cnt Score Error Units Reflectionfield. getInt_instance_field AVGT 10 6.361 ± 0.002 ns/op GetInt_instance_field_var avgt 10 5.976 ± 0.112 ns/op Reflectionfields. getInt_static_field AVgt 10 5.946 ± 0.003 ns/op Reflectionfield. getInt_static_field_var avgt 10 6.372 ± 0.014 ns/op Reflectionfield. setInt_instance_field avgt 10 4.672 ± 0.013 ns/op Reflectionfield. setInt_instance_field_var avgt 10 3.933 ± 0.009 ns/op Reflectionfield. setInt_static_field AVGT 10 4.661 ± 0.001 ns/op SetInt_static_field_var avgt 10 3.953 ± 0.014 ns/op ReflectionMethods.class_forName_1arg avgt 10 Reflectionmethods. class_forName_1arg_var AVGT 10 402.458 ± 0.418 ns/op 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 Static_var AVGT 10 13.164 ± 0.046 ns/opCopy the code

Risks and assumptions

Code that is highly dependent on existing implementations and that is not documented may be affected. Compatibility to mitigate this risk, as a workaround, you can use – Djdk. Reflect the useDirectMethodHandle = false to enable the realization of the old.

  • Internally generated reflection classes (i.eMagicAccessorImplSubclass) code will no longer be valid and must be updated.
  • Method handle calls can consume more resources than older reflection implementations. Such calls involve calling multiple Java methods to ensure that a member’s declared class is initialized prior to access, so more stack space may be required for the necessary execution frames. This could lead toStackOverflowError, or if thrown when the class is initializedStackOverflowError, will lead toNoClassDefFoundError.