Monday, June 23, 2025
HomeJavaTechnique handles: A greater method to do Java reflection

Technique handles: A greater method to do Java reflection


Oracle Java, Java Certification, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials


Java 18 launched methodology handles, which provide a greater method to do reflective programming.

Word that to keep up backward compatibility, the Reflection API have to be maintained with all its flaws and historic baggage, so I’m speaking solely about inner adjustments right here.

Why is there a brand new implementation?

Java 17 and earlier implementations of reflection depend on a delegation sample—particularly a category known as DelegatingMethodAccessorImpl. The delegate for this class begins off as a category that depends on native code to carry out reflective invocation. Nonetheless, as soon as a threshold worth is handed, the delegate is changed by a customized class (it’s mentioned that it has been patched out). This practice class is created dynamically at runtime, and this can be a comparatively costly operation, which is why it isn’t carried out till an invocation threshold is handed.

This implementation is usually known as the inflating implementation as a result of the native delegate is inflated to a customized bytecode implementation, eradicating the necessity for a local name. This works properly sufficient, however the inflating implementation is reasonably complicated, because it has two separate code paths for reflection—one for native strategies and one for the customized class—that are dynamically generated bytecode stubs.

Not solely that, however as a result of complexities of bytecode verification, the dynamically spun reflective methodology accessors should be special-cased by the JVM via the MagicAccessorImpl class. This provides but extra complexity to an already considerably baroque system.

General, these particulars imply that there’s a lot of code to keep up, particularly alongside the brand new MethodHandles API, which additionally offers comparable capabilities for reflection.

This results in an intriguing risk: What if everything of the inflating implementation may very well be changed by an equal functionality primarily based upon methodology handles? The related plan for this concept is JEP 416: Reimplement core reflection with methodology handles, which was delivered in Java 18.

The remainder of this text will dive deeper into the brand new implementation and distinction it with the present inflating implementation, initially by a related safety mechanism.

Enhanced discipline filtering

Because the very first launch of OpenJDK 7, the reflection subsystem has had the power to filter out sure fields from being seen to customers, even when reflection and setAccessible() are used. That is achieved by sustaining a set of discipline names in an inner Reflection helper class; these discipline names should not allowed to look within the returned worth for getDefinedFields().

In Java 7 and Java 8, not many fields are filtered—mainly solely these required to guard a Java safety supervisor (if one is ready) and the filtering mechanism itself.

Java 11 produced the well-known warning, “An unlawful reflective entry operation has occurred,” however the filtering mechanism didn’t change a lot from Java 8, besides that it moved into a brand new, nonexported jdk.inner.replicate package deal within the java.base module.

Nonetheless, the arrival of Java 12 in March 2019 altered issues considerably. The code in jdk.inner.replicate.Reflection was up to date to considerably enhance the variety of fields which can be inaccessible to reflective code, as you’ll be able to see within the following:

/** Used to filter out fields and strategies from sure lessons from public

    view, the place they’re delicate or they might comprise VM-internal objects.

    These Maps are up to date very not often. Fairly than synchronize on

    every entry, they use copy-on-write */

personal static risky Map<Class<?>, Set<String>> fieldFilterMap;

personal static risky Map<Class<?>, Set<String>> methodFilterMap;

personal static last String WILDCARD = “*”;

public static last Set<String> ALL_MEMBERS = Set.of(WILDCARD);

static {

    fieldFilterMap = Map.of(

        Reflection.class, ALL_MEMBERS,

        AccessibleObject.class, ALL_MEMBERS,

        Class.class, Set.of(“classLoader”),

        ClassLoader.class, ALL_MEMBERS,

        Constructor.class, ALL_MEMBERS,

        Discipline.class, ALL_MEMBERS,

        Technique.class, ALL_MEMBERS,

        Module.class, ALL_MEMBERS,

        System.class, Set.of(“safety”)

    );

    methodFilterMap = Map.of();

}

Word that entry to those fields is dealt with otherwise than entry to normal fields.

$ j11

openjdk model “11.0.13” 2021-10-19

OpenJDK Runtime Atmosphere Temurin-11.0.13+8 (construct 11.0.13+8)

OpenJDK 64-Bit Server VM Temurin-11.0.13+8 (construct 11.0.13+8, blended mode)

$ java javamag.reflection.ex2.ReflectTheReflect

class java.lang.replicate.Technique

Whats up world

WARNING: An unlawful reflective entry operation has occurred

WARNING: Unlawful reflective entry by javamag.reflection.ex2.ReflectTheReflect (file:/Customers/ben/initiatives/writing/Oracle/Articles/reflection/src/primary/java/) to discipline java.lang.replicate.Technique.methodAccessor

WARNING: Please contemplate reporting this to the maintainers of javamag.reflection.ex2.ReflectTheReflect

WARNING: Use –illegal-access=warn to allow warnings of additional unlawful reflective entry operations

WARNING: All unlawful entry operations shall be denied in a future launch

class jdk.inner.replicate.DelegatingMethodAccessorImpl

$ j12

openjdk model “12.0.2” 2019-07-16

OpenJDK Runtime Atmosphere AdoptOpenJDK (construct 12.0.2+10)

OpenJDK 64-Bit Server VM AdoptOpenJDK (construct 12.0.2+10, blended mode, sharing)

$ java javamag.reflection.ex2.ReflectTheReflect

class java.lang.replicate.Technique

Whats up world

java.lang.NoSuchFieldException: methodAccessor

   at java.base/java.lang.Class.getDeclaredField(Class.java:2416)

   at javamag.reflection.ex2.ReflectTheReflect.primary(ReflectTheReflect.java:16)

The sensible impact of those adjustments is that from Java 12 onwards, some fields in some key lessons in java.base are fully inaccessible to consumer code, even when reflection is used. Even Unsafe doesn’t assist; harmful strategies such because the “static offset” strategy now not work, as they require a reflective Discipline object to acquire the offset.

The sector and the tactic filter are a safety requirement however whereas they do encapsulate the internals, notice that they aren’t straight associated to the robust encapsulation supplied by modularity (which is what individuals sometimes consider after they consider robust encapsulation of Java’s internals).

For instance, as you’ll be able to see from the instance above, this modification considerably predates the final enforcement of JEP 396: Strongly encapsulate JDK internals by default, which appeared in Java 16 (March 2021). Discipline filtering can also’t be labored round with choices (similar to –add-opens), whereas the encapsulation from modularity sometimes might be.

One results of the improved discipline filter is that the one possible way for a Java developer to see the brand new implementation of reflection is by utilizing an IDE debugger. These instruments can entry inner JVM information buildings at a a lot deeper stage than is feasible with Java code, however they’re much much less handy than simply working some reflective Java code.

Technique handles

Technique handles are supposed for use in comparable circumstances the place a programmer may attain for the Reflection API.

Nonetheless, by gleaning from over 20 years of sensible expertise since reflection debuted, this second try at a reflective invocation functionality is meant to supply (amongst different issues) a greater API for direct use.

Technique kind objects. One helpful place to begin for this higher API is to think about that reflection represents the sort signature of strategies as cases of Class[]. As famous in “Reflection for the fashionable Java programmer,” it’s because the Reflection API predates the Collections API (and, sadly, given Java’s excessive concentrate on backward compatibility, that is an API that can’t be withdrawn).

Nonetheless, within the MethodHandles API, that is achieved with the MethodType class as a substitute, which avoids the issue of utilizing arrays as area objects. MethodType is a category that has immutable cases which can be created from a static manufacturing unit methodology that’s variadic in school objects, as follows:

public static MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>… ptypes) {

    // …

}

For instance, the article that represents the evaluate() methodology for a string comparator is obtained as follows:

var mtStringComparator = MethodType.methodType(int.class, String.class, String.class);

The primary argument to methodType() is the return kind of the tactic—which is, on this case, int. After that, the varieties of the arguments to the tactic observe in positional order.

Discover that this MethodType doesn’t embody both the tactic identify or the kind of the article that the tactic shall be known as upon. This truly makes it a extra helpful abstraction as a result of it may be utilized in circumstances similar to lambdas (the place the tactic’s identify doesn’t matter); due to this fact, it may be used for each static and digital strategies.

Lookup objects. A second difficulty that methodology handles resolve is the query of setAccessible(), which lets you break the principles of the Java language by permitting you to name code to selectively disable entry management.

Having this functionality was an express nongoal for methodology handles. As a substitute, the designers needed to ensure that encapsulation may very well be correctly protected within the new mechanism.

To attain this safety, a brand new idea known as a lookup context was launched. To know how this works, recall that each one Java code executes inside a technique, and that each methodology lives inside a category. Subsequently, executing code runs inside a category—and so there’s a set of strategies that the code might name.

For instance, the code might name

◉ Any personal methodology of the present class (or an enclosing class)

◉ Any public methodology of a public class

Equally, this is applicable for the package-private and guarded strategies, in response to the principles of the Java language. (The power to name public strategies can be affected by the modules system in current variations of Java.)

The purpose of a lookup context is to encapsulate the data of which strategies it’s authorized to name on the level the place the lookup object is created. Inaccessible strategies should not seen from the lookup context, which removes the excellence that reflection has between getMethod() and getDeclaredMethod().

As a result of the lookup object encapsulates a functionality to lookup strategies and fields, it is advisable to watch out about sharing the article, particularly with untrusted code.

The MethodHandles class additionally has a publicLookup() methodology which may be preferable, as it’s a minimally trusted lookup that may be freely shared however is restricted in what it will probably lookup.

The commonest method to acquire a lookup context is to name the static helper methodology MethodHandles.lookup(), which returns an object that represents strategies accessible from the present class. After you have the lookup object, you’ll be able to acquire methodology handles from it by calling certainly one of its discover*() strategies, similar to findVirtual() or findConstructor().

For instance, you’ll be able to acquire a technique deal with for toString() as follows:

// Outline a technique kind object equivalent to toString()

// That is the tactic kind for strategies that return String and take no parameters

var mt = MethodType.methodType(String.class);

System.out.println(“MT: “+ mt);

// Create a lookup object for the present class context

var lk = MethodHandles.lookup();

strive {

  // Create a technique deal with for toString()

  var mh = lk.findVirtual(getClass(), “toString”, mt);

  System.out.println(“mh.MT: “+ mh.kind());

  // … do one thing with the tactic deal with

} catch (NoSuchMethodException | IllegalAccessException mhx) {

  throw new RuntimeException(mhx);

}

As a result of toString() is all the time public, the lookup ought to all the time discover the tactic. Nonetheless, there’s one other level about methodology varieties right here, which you’ll be able to see by trying on the output of the next two println() statements:

MT: ()String

mh.MT: (MTMain)String

These present that after an MHs.Lookup.findVirtual() name, the MethodType given by MH.kind() can have the receiver kind as the primary argument kind. In different phrases, lookups don’t require the receiver kind to be manually added, however as soon as a technique deal with has been resolved, the receiver kind (assuming there’s one) is understood.

Invoking a technique deal with. Within the instance above, I used findVirtual() as a result of I wish to name the proper override of toString() when invoking the tactic deal with. To see this in motion, outline a easy class, MTMain, with an implementation of toString(), as follows:

public class MTMain {

    @Override

    public String toString() {

        return “MTMain {}”;

    }

    public static void primary(String[] args) {

        var mh = getToStringHandle();

        var primary = new MTMain();

        strive {

            var s = mh.invoke(primary);

            System.out.println(s);

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

    public static MethodHandle getToStringHandle() {

        // Performs the lookup proven within the earlier instance

        // and returns the tactic deal with

    }

}

This, as anticipated, has the identical general impact as calling System.out.println(primary.toString()). Nonetheless, there are some necessary variations. Take into account the very comparable (however reflective) class RefMain.

public class RefMain {

    @Override

    public String toString() {

        return “RefMain {}”;

    }

    public static void primary(String[] args) {

        var primary = new RefMain();

        var mh = getToStringMethod(primary);

        strive {

            var s = mh.invoke(primary);

            System.out.println(s);

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

    public static Technique getToStringMethod(Object o) {

        Class<?> clazz = o.getClass();

        strive {

            return clazz.getMethod(“toString”);

        } catch (NoSuchMethodException e) {

            throw new RuntimeException(e);

        }

    }

}

public static void primary(java.lang.String[]);

    Code:

       0: new           #9                  // class javamag/reflection/ex1/RefMain

       3: dup

       4: invokespecial #11                 // Technique “<init>”:()V

       7: astore_1

       8: aload_1

       9: invokestatic  #12                 // Technique getToStringMethod:(Ljava/lang/Object;)Ljava/lang/replicate/Technique;

      12: astore_2

      13: aload_2

      14: aload_1

      15: iconst_0

      16: anewarray     #2                  // class java/lang/Object

      19: invokevirtual #16                 // Technique java/lang/replicate/Technique.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

      22: astore_3

      23: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;

      26: aload_3

      27: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V

It’s quite similar to the method handle case’s bytecode, up until #15.

public static void main(java.lang.String[]);

    Code:

       0: new           #9                  // class javamag/reflection/mh/MTMain

       3: dup

       4: invokespecial #11                 // Technique “<init>”:()V

       7: astore_1

       8: aload_1

       9: invokestatic  #12                 // Technique getToStringHandle:(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;

      12: astore_2

      13: aload_2

      14: aload_1

      15: invokevirtual #16                 // Technique java/lang/invoke/MethodHandle.invoke:(Ljavamag/reflection/mh/MTMain;)Ljava/lang/Object;

      18: astore_3

      19: getstatic     #22                 // Discipline java/lang/System.out:Ljava/io/PrintStream;

      22: aload_3

      23: invokevirtual #28                 // Technique java/io/PrintStream.println:(Ljava/lang/Object;)V

Oracle Java, Java Certification, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials

Discover there’s one thing totally different in regards to the signature of Technique.invoke() in comparison with MethodHandle.invoke() within the bytecode. The reflective case has a signature of

Technique java/lang/replicate/Technique.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

which exactly corresponds to the following source code in Method.java:

// Method.java

    public Object invoke(Object obj, Object… args)

        throws IllegalAccessException, IllegalArgumentException,

           InvocationTargetException

    {

        // …

    }

This is quite different from the MethodHandle case, which (from the bytecode) has a signature like the following, which means: “a method that acts on an object of type MTMain and returns Object.”

Method java/lang/invoke/MethodHandle.invoke:(Ljavamag/reflection/mh/MTMain;)Ljava/lang/Object;

However, the source code for invoke() on a method handle is declared as follows:

// MethodHandle.java

public final native @PolymorphicSignature Object invoke(Object… args) throws Throwable;

What’s going on? The answer is in the annotation @PolymorphicSignature—which indicates that invoke() (and a couple of other methods of MethodHandle) is signature polymorphic.

Per the Java Language Specification, a signature polymorphic method is one that can operate with essentially any argument and return types. When the Java source code compiler encounters a call to a signature polymorphic method, the code will compile, regardless of how the method is being called. Effectively, it is as though a signature polymorphic method is not a single method, but an entire family of methods with every possible signature available.

To illustrate this, the following code alludes to the string comparator example shown earlier:

public int compareTwoStrings(String s1, String s2) {

    return 0;

}

private static MethodHandle getStringCompHandle() {

    // MethodType of compareTwoStrings()

    var mt = MethodType.methodType(int.class, String.class, String.class);

    var lk = MethodHandles.lookup();

    try {

        return lk.findVirtual(lk.lookupClass(), “compareTwoStrings”, mt);

    } catch (NoSuchMethodException | IllegalAccessException mhx) {

        throw new RuntimeException(mhx);

    }

}

This example uses the lookupClass() method present on the lookup object. Here, this method returns the class object of the current class, but it works from both static and instance methods.

Here’s a bit of code to drive this example.

var mhSc = getStringCompHandle();

try {

    var s = mhSc.invoke(main, “foo”, “bar”);

    System.out.println(s);

} catch (Throwable e) {

    e.printStackTrace();

}

Sure enough, the bytecode for this snippet shows the signature polymorphic call to invoke().

Method java/lang/invoke/MethodHandle.invoke:(Ljavamag/reflection/mh/MTMain;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;

This is all fine at compile time, but what about at runtime? For example, what happens if you try to invoke the method handle with the wrong arguments, as in the following:

var s = mhSc.invoke(main, “foo”);

Well, this will cause an exception and, helpfully, the JVM reports that the method handle you are attempting to call is expecting three arguments—one of type MTMain and two strings—but it was called with only an MTMain and one String.

java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(MTMain,String,String)int to (MTMain,String)Object

These examples show that the method handle approach has more information in the bytecode to help the runtime, and it avoids some of the overhead that is present in reflective calls. In particular, the boxing of primitive method arguments and collecting the arguments into an array can be avoided because the shape of the method call is known.

Rebasing reflection on top of method handles

Method handles can be used as a replacement for reflection in modern Java code, because they have essentially the same set of capabilities. There are some differences—method name changes and, in particular, the use of lookup objects—but overall, Java programmers who are already familiar with reflection should not experience any serious difficulties coming to grips with method handles.

All of this raises an intriguing possibility—could method handles be used as the engine to perform introspection operations, with the old Reflection API retrofitted on top of it?

Yes! In fact, JDK 18 did indeed switch over to using method handles to implement reflection, without changing the interface.

The old (Java 1.1) reflection interface must be maintained because Java always tries to retain backward compatibility, but the implementing code can be changed if that constraint holds.

The MethodAccessor interface is maintained, but instead of the MethodAccessorImpl classes that you are already familiar with, java.lang.reflect.Method instead contains evidence of method handles, as you can see in the source code.

public MethodAccessor newMethodAccessor(Method method, boolean callerSensitive) {

    // use the root Method that will not cache caller class

    Method root = langReflectAccess.getRoot(method);

    if (root != null) {

        method = root;

    }

    if (useMethodHandleAccessor()) {

        return MethodHandleAccessorFactory.newMethodAccessor(method, callerSensitive);

    } else {

        if (noInflation() && !method.getDeclaringClass().isHidden()) {

            return generateMethodAccessor(method);

        } else {

            NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method);

            return acc.getParent();

        }

    }

}

The method handles implementation is the default—and so useMethodHandleAccessor() will default to true—but the old inflating implementation is temporarily still available. It can be activated via the following switch, but this is purely for compatibility reasons and will be removed in a future JDK release:

-Djdk.reflect.useDirectMethodHandle=false

Unfortunately, demonstrating the existence of the new implementation from within Java code is not very easy. As you saw earlier in the article, recent Java releases have started removing certain fields from the list of defined fields returned by introspection. This has the effect of making these fields impossible to access reflectively, and so you can’t do the sort of tricks shown in “The performance implications of Java reflection” to show the method handles present inside Method objects.

Finally, a word on performance: It might be tempting to pose the question, “How does the new implementation compare with the original, inflating code?” There is no simple, well-defined answer. Many aspects of the reflection subsystem have changed (such as access control, boxing, and volatile access to accessors), and the overall aggregate effect is impossible to reason about.

I could provide some Java Microbenchmark Harness (JMH) benchmarks that purport to show the difference between the two implementations, but everything I wrote in the previous article about microbenchmarks—that they represent individual data points and not a representation of some deeper underlying truth—continues to apply.

Instead, remember the somewhat mundane truth at the heart of performance analysis: If you want to know the performance impact of a particular language or JVM feature on your specific application within a certain range of inputs, you will have to test your specific application.

Conclusion

This series of articles provided an in-depth tour of reflection. As well as discussing the APIs, it covered reflection as a runtime technology that shows the real difference between the statically typed Java language and the dynamic nature of the JVM.

I’ve discussed how reflection is implemented, and I introduced a little of the JVM internals that support the capability. This included showing how recent releases of Java have closed off access to the reflection internals from meddling programmers, which allows the platform developers to make changes more freely.

This final article introduced the new MethodHandles API and showed how this has been used to reimplement the Reflection API. It also showed how it can be used directly as a more convenient and modern way to do introspection.

Source: oracle.com

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments