Thursday, April 25, 2024
HomeJavaThe efficiency implications of Java reflection

The efficiency implications of Java reflection


Reflection slows down your Java code. Why is that?

Reflection is highly effective—and sometimes misunderstood. This text will construct on the introduction of the Core Reflection API launched in “Reflection for the fashionable Java programmer” and talk about two main extra matters: how reflection is applied within the HotSpot JVM and the modifications made to reflection in latest variations of the Java platform.
Oracle Java Career, Java Skills, Java Jobs, Java Tutorial and Materials, Java Guides, Java Learning

This dialogue begins by exploring a simplified type of the reflection mechanism’s code from the JDK. The code within the following examples resembles the implementation in Java 8, however some complexity has been eliminated for readability. This primary model is utilized in all however the latest Java variations. (A future article will have a look at the most recent modifications.)

Begin by trying on the invoke() technique on Technique, which appears to be like like the next:

public Object invoke(Object obj, Object… args)

    throws IllegalAccessException, IllegalArgumentException,

           InvocationTargetException {

  if (!override) {

    if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {

      Class<?> caller = Reflection.getCallerClass();

      checkAccess(caller, clazz, obj, modifiers);

    }

  }

  MethodAccessor ma = methodAccessor;

  if (ma == null) {

    ma = acquireMethodAccessor();

  }

  return ma.invoke(obj, args);

}

The code first checks to see if the override flag has been set; it’s going to have been set if setAccessible() has been referred to as. Subsequent, a reference to a MethodAccessor object is obtained, after which the invoke() name is delegated to it.

(Be aware: Lots of the lessons on this part usually are not in an API package deal of java.base, in order that they can’t be referred to as straight in fashionable Java code. For instance, MethodAccessor is in jdk.inner.replicate.)

The MethodAccessor interface is the important thing to the reflective invocation functionality. It acts as a simple delegate.

public interface MethodAccessor {

    /** Matches specification in {@hyperlink java.lang.replicate.Technique} */

    public Object invoke(Object obj, Object[] args)

        throws IllegalArgumentException, InvocationTargetException;

}

The primary time this code known as, the strategy acquireMethodAccessor() creates an occasion of the sort DelegatingMethodAccessorImpl that implements MethodAccessor, as follows:

import java.lang.replicate.InvocationTargetException;

/** Delegates its invocation to a different MethodAccessorImpl and might

    change its delegate at runtime. */

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {

  personal MethodAccessorImpl delegate;

  DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {

    setDelegate(delegate);

  }

  public Object invoke(Object obj, Object[] args)

        throws IllegalArgumentException, InvocationTargetException {

    return delegate.invoke(obj, args);

  }

  void setDelegate(MethodAccessorImpl delegate) {

    this.delegate = delegate;

  }

}

Because the remark explains, the aim of this class is to behave as an acceptable quantity of indirection and supply a delegation level that may be up to date at runtime. The preliminary delegate is an occasion of the category NativeMethodAccessorImpl.

class NativeMethodAccessorImpl extends MethodAccessorImpl {

  personal Technique technique;

  personal DelegatingMethodAccessorImpl father or mother;

  personal int numInvocations;

  // …

  public Object invoke(Object obj, Object[] args)

          throws IllegalArgumentException, InvocationTargetException {

    if (++numInvocations >

          ReflectionFactory.inflationThreshold()) {

      MethodAccessorImpl acc = (MethodAccessorImpl)

          new MethodAccessorGenerator()

            .generateMethod(technique.getDeclaringClass(),

                            technique.getName(),

                            technique.getParameterTypes(),

                            technique.getReturnType(),

                            technique.getExceptionTypes(),

                            technique.getModifiers());

        father or mother.setDelegate(acc);

    }

    return invoke0(technique, obj, args);

  }

  personal static native Object invoke0(Technique m, Object obj, Object[] args);

  // …

}

This code accommodates an if block that will likely be entered after an invocation threshold is reached, similar to after the reflective technique has been referred to as a sure variety of instances. If the invocation threshold has not but been reached, the code proceeds with the native name.

As soon as the brink has been reached, NativeMethodAccessorImpl will use a code era manufacturing unit, contained in MethodAccessorGenerator.generateMethod(), to create a customized class that accommodates bytecode that calls the goal of the reflective name.

After creating an occasion of this dynamically created class, the decision to setDelegate() makes use of an uplevel reference to the father or mother accessor to exchange the present object with acc, the newly created customized object.

For technical causes associated to class verification, the JVM should pay attention to the particular nature of the reflective accessor lessons. For that reason, there’s a particular accessor class within the inheritance hierarchy that acts as a marker for the JVM. The exact particulars of this needn’t concern you, so don’t fear.

Total, the mechanism as described represents a efficiency trade-off—some reflective calls are made just a few instances, so the code era course of may very well be very costly or wasteful. Alternatively, switching from Java right into a native name is slower than remaining in pure Java. This strategy permits the runtime to keep away from code era till it appears possible that the reflective name will likely be made comparatively usually.

Consequently, the prices of code era can then be amortized over the lifetime of this system, whereas nonetheless offering higher efficiency for later calls than the native implementation can obtain.

Reflecting on reflection

You may see extra of the reflection subsystem in motion (and a few latest modifications) by reflecting on the subsystem itself. Assume the category has the next two strategies on it—one is an easy technique that simply prints a message, and one is a reflective accessor for it:

public static void printStr() {

    System.out.println(“Good day world”);

}

public Technique getMethodObj() throws NoSuchMethodException {

    var selfClazz = getClass();

    var toStr = selfClazz.getMethod(“printStr”);

    return toStr;

}

Moreover, there’s the next calling code (exception dealing with has been elided for readability):

var m = self.getMethodObj();

Class<?> mClazz = m.getClass();

System.out.println(mClazz);

// That is vital as a consequence of some points of lazy analysis

m.invoke(null);

var f = mClazz.getDeclaredField(“methodAccessor”);

f.setAccessible(true);

Object ma = f.get(m);

System.out.println(ma.getClass());

The code above produces the next output when run with Java 11:

$ java javamag.reflection.ex2.ReflectTheReflect

class java.lang.replicate.Technique

Good day world

WARNING: An unlawful reflective entry operation has occurred

WARNING: Unlawful reflective entry by javamag.reflection.ex2.ReflectTheReflect (file:/Customers/ben/tasks/writing/Oracle/Articles/reflection/src/foremost/java/) to subject 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 will likely be denied in a future launch

class jdk.inner.replicate.DelegatingMethodAccessorImpl

There are two issues to pay attention to right here.

◉ First, the preliminary reflective invocation of the strategy is required. If that preliminary invocation is omitted, the code will fail. This is because of lazy initialization of the reflection subsystem: For efficiency causes, the Technique objects usually are not populated until they’re wanted.

◉ Second, should you now swap to Java 17, the code will fail with the next output:

$ java javamag.reflection.ex2.ReflectTheReflect

class java.lang.replicate.Technique

Good day world

java.lang.NoSuchFieldException: methodAccessor

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

       at javamag.reflection.ex2.ReflectTheReflect.foremost(ReflectTheReflect.java:15)

How reflection impacts efficiency

It’s simple to think about that the flexibleness of reflection comes at a value by way of runtime efficiency. The apparent, speedy query that involves thoughts is that this: “How huge is that price?” Nevertheless, this query carries a hidden assumption—that the query is significant and well-formed within the first place. It’s not at all times that simple.

You may, in fact, write a Java Microbenchmark Harness (JMH) benchmark that compares a reflective name to a direct one. Such a benchmark might look a bit like the next. The JMH annotations could be discovered within the package deal org.openjdk.jmh.annotations.

@State(Scope.Thread)

@BenchmarkMode(Mode.Throughput)

@OutputTimeUnit(TimeUnit.SECONDS)

public class SimpleReflectionBench {

    personal static Technique getTime = null;

    personal static Object o = null;

    static {

        attempt {

            var clazz = ReflectionHolder.class;

            var ctor = clazz.getConstructor();

            o = ctor.newInstance();

            getTime = clazz.getMethod(“getTime”);

        } catch (Exception x) {

            throw new RuntimeException(x);

        }

    }

    @Benchmark

    public lengthy runReflective() throws InvocationTargetException, IllegalAccessException {

        Object ret = getTime.invoke(o);

        return (lengthy)ret;

    }

    @Benchmark

    public lengthy runDirect() {

        var choose = (ReflectionHolder)o;

        return choose.getTime();

    }

    static class ReflectionHolder {

        public ReflectionHolder() {}

        public lengthy getTime() {

            return System.currentTimeMillis();

        }

    }

}

The main points will fluctuate a bit relying on which {hardware} platform you might be utilizing, however a typical end result will likely be on the order of a 23% hit to efficiency.

However no actual Java program consists of only a single name. In actuality, this code runs inside an software course of (the JVM), and you’ll’t simply isolate the efficiency of the executing software code from the JIT compiler, reminiscence administration, and different runtime subsystems current in that software course of.

As well as, the JIT compiler performs heavy optimization and transformation of this system—and the exact particulars of how this system is remodeled rely very a lot on this system.

For instance, one of the vital highly effective transformations that the JIT compiler performs is computerized technique inlining. That is, in truth, one of many first transformations to be carried out, as a result of combining technique our bodies brings extra code into the view of the JIT compiler. This probably permits optimizations that will not have been doable if the JIT compiler might look solely on the code of a single technique at a time.

Sadly, reflective calls usually are not usually inlined within the basic case, as a consequence of their dynamic nature. Be aware the phrase usually, as there are caveats that apply to this assertion.

As you’ve already seen, the reflection implementation generates Java bytecode (utilizing MethodAccessorGenerator.generateMethod()) for the decision. This could make it simpler for the JIT compiler to inline the reflective name if sure situations maintain, such because the Technique object being rooted in a static last subject and the goal technique being static (or having a undoubtedly recognized receiver sort).

Total, because of this the precise overhead of reflective calls will not be simple to motive about from first rules, however it may possibly simply be a lot, a lot bigger than 23% because of the operation of inlining on direct calls, which isn’t actually doable for equal reflective calls.

The principle conclusion this results in is that you should query what a simplistic determine similar to 23% means—or if it means something in any respect.

To cite Brian Goetz, “The scary factor about microbenchmarks is that they at all times produce a quantity, even when that quantity is meaningless. They measure one thing; we’re simply undecided what.”

The small-scale efficiency results are successfully smoothed out by coping with a bigger combination (that’s, the entire system or a subsystem), however that makes it very exhausting or unattainable to make basic, code-level suggestions for writing performant functions.

You may say that software efficiency is an emergent phenomenon, because it arises from the pure scale of your functions (probably 1000’s of lessons or thousands and thousands of traces of code), and there may be not essentially any single root trigger (or small set of root causes) for a particular achieve or loss.

This reality is what leads efficiency professionals to induce builders to easily write good, clear code and let the runtime care for optimization.

A efficiency tip is very often only a workaround for some quirk of the runtime—and if software builders are conscious of the quirk, it’s fairly possible that so are the JVM builders—and that they’re working to repair it. For example this, within the case of reflection, you’ll be able to ask, “What JVM-level mechanisms are concerned that might complicate the general image of the impact?”

Determine 1 exhibits a single line of Java code that executes a reflective name on a Technique object denoted as M, with arguments x and y. It has been annotated to point out the main points of runtime conduct that may have a efficiency affect on the execution of the decision.

Oracle Java Career, Java Skills, Java Jobs, Java Tutorial and Materials, Java Guides, Java Learning

Determine 1. JVM results on reflection

Listed here are 4 huge areas that might have an effect on efficiency.

◉ Boxing happens at a number of totally different locations.

◉ The decision web site for a reflective name is claimed to be megamorphic (many doable implementations for the strategy) as a result of within the implementation as much as Java 17, every occasion of Technique has a unique, dynamically spun technique accessor object.

◉ The methodAccessor subject on Technique is risky, so rereading it’s obligatory. Subsequently, the invoke() name results in an indirection and a digital dispatch on the delegate.

◉ Technique accessibility checks are carried out on every name.

To dig somewhat deeper, study the signature of the invoke() technique on Technique.

public Object invoke(Object obj, Object… args)

        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

That is probably the most basic signature for a Java technique you could write—and it must be. Reflective calls may very well be of any signature, and that sort info is not going to, basically, be accessible till runtime. Subsequently, if Technique represents the potential to name a way, and invoke() represents the precise name, it’s solely logical that the signature have to be this basic.

Java’s sort system will not be single-rooted, so any primitives that seem will likely be dealt with by way of boxing.

Individually, the accessibility test is essential, as a result of entry management is generally checked at classloading time (and any poorly behaved lessons that try and infringe on it usually are not loaded). Use of reflective code modifications this image, and the Core Reflection API has a few weaknesses (or vital evils, relying on how you are feeling about them).

◉ You may get a mirrored image object equivalent to a way that you wouldn’t be capable to name straight.

◉ You may break the principles of the Java language by permitting calling code to selectively disable entry management utilizing setAccessible().

Subsequently, the implementation of Technique should test whether or not the Technique object requires an entry management test on each name (and carry out it, in that case). It’s vital to do that on each name as a result of the Technique object might have had setAccessible() referred to as on it after the earlier name.

These accessibility checks affect efficiency, as you’ll be able to see by modifying the benchmark as follows:

static {

        attempt {

            var clazz = ReflectionHolder.class;

            var ctor = clazz.getConstructor();

            o = ctor.newInstance();

            getTime = clazz.getMethod(“getTime”);

            // Disable entry management checking

            getTime.setAccessible(true);

        } catch (Exception x) {

            throw new RuntimeException(x);

        }

    }

Evaluating the outcomes from this benchmark to the reflective benchmark within the earlier case exhibits {that a} name that’s not checked for entry management appears to run sooner. Nevertheless, the general combination impact remains to be unknown, so you should keep away from making any doubtful conclusions in regards to the basic usefulness of disabling entry management for reflective calls.

As said beforehand, the correct object of research for efficiency is the entire software. This implies that you would, for instance, recompile the JDK with a code change in order that entry management is at all times ignored. This might, in idea, give a greater quantity for not less than one side of reflective efficiency in combination.

Nevertheless, should you did do that, can you actually make sure that globally disabling reflective entry management checks doesn’t change semantics anyplace within the software—or within the libraries that it relies upon upon? In any case, maybe a framework that you simply’re utilizing has a intelligent technique that probes doable strategies for reflective invocation and depends on the entry management semantics to enhance efficiency.

If it did, how would you realize?

Supply: oracle.com

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments