The Reflections API has been with us since the earliest days of Java. The Class class was available right from the start but it wasn't all that useful. JDK 1.1 introduced listing of all members defined within a particular class and a way to indirectly invoke methods and to modify values of class fields. It was now possible to develop frameworks like Spring and Hibernate enriching the existing code thanks to metaprogramming features. While these solutions were useful they weren't particularly convenient due to the XML based configuration.

With JDK 1.5 came the next important piece of the puzzle - annotations. This was the moment to say goodbye to XML and to place configuration right next to the code itself. Instead of specifying the fully qualified name of a class or a method we could finally just mark the target element with all necessary metadata. While annotations don't improve performance they certainly do make things a lot easier.

Reflective access boosts software development efficiency a great deal but there's a price we have to pay. Java compilation process is great at optimizing direct access code. It can perform dead code elimination, method inlining and machine code recompilation based on runtime statistics. All of that is off the table for pieces of code using the java.lang.reflect package.

OpenJDK 8 (Windows 10)
  Score Error
Direct access 3.469 0.196
Reflections 6.505 0.620

OpenJDK 11.0.6 (Windows 10)
  Score Error
Direct access 3.476 0.329
Reflections 5.786 0.160

Score represents the average time in nanoseconds per single operation. Error uses the same unit.

Method.invoke() on OpenJDK 8 is 87% slower than direct access. On OpenJDK 11 performance has been improved a bit and the gap has been decreased to 66%. There is some progress but it will never be as fast as the normal code.

All benchmark data in this article has been generated using Java Microbenchmark Harness by calling in various ways the same getter method. Full code is available on Github. Each test was run 10 times in total with each run lasting for 5 seconds.

Method handles

In JDK 7 a new bytecode instruction invokedynamic (also known as indy) has been introduced together with the MethodHandle class and its subtypes to represent the linked behaviour. Java developers might not have noticed right away any change as this new instruction was not used directly by Java just yet. It was only the first step towards lambda expression support. However, these two elements were crucial for dynamic languages like Groovy.

You can think of MethodHandle as a new, enriched alternative to the standard Method class. It can represent any method including the static ones, field getters and setters and constructors.

When JVM encounters the invokedynamic instruction in bytecode it first executes a special bootstrap method which returns a CallSite object containing the target MethodHandle. Using that bootstrap method dynamic languages can determine the final behaviour to be executed based on the method name and types of the provided parameters.

While we can't directly use indy in Java code we can easily transform Method instances into MethodHandle ones.

Method reflectionMethod = Object.class.getMethod("toString");
MethodHandle handle = MethodHandles.lookup().unreflect(reflectionMethod);

Let's see how the new solution fares performance-wise.

OpenJDK 8 (Windows 10)
  Score Error
Direct access 3.469 0.196
Reflections 6.505 0.620
Handle 7.183 1.132

OpenJDK 11.0.6 (Windows 10)
  Score Error
Direct access 3.476 0.329
Reflections 5.786 0.160
Handle 7.309 0.677

Turns out the new approach is two times slower than direct access. Surprisingly invoking methods indirectly using MethodHandle is slower than the original Reflections API.

Lambda Metafactory

JDK 8 introduced lambda expressions and the world became a better place. A simple listener could now be registered using just one line of code instead of at least six as it was the case with anonymous classes. Complex operations on collections became more readable thanks to the Stream API. Soon reactive libraries began to emerge.

On the surface it seems like lambda expression is just syntactic sugar for anonymous classes. In reality the process is more complex and it involves both invokedynamic and code generation during compilation to bytecode.

All the real magic happens in the LambdaMetafactory class. It is used to transform any direct method handle into an implementation of the target functional interface.

What's important this class is a part of the public API so there's nothing stopping us from using it in our code. We can retrieve a Method using standard Reflections API, unreflect it to MethodHandle and then use the LambdaMetafactory to produce an object implementing the desired interface which then can be called as any other code. Implementing the full transformation process is not that easy but it sure does pay off.

Let's say we have the following code.

String toBeTrimmed = " text with spaces ";
Supplier<String> lambda = toBeTrimmed::trim;

The code below shows how we can replicate the same behaviour ourselves.

String toBeTrimmed = " text with spaces ";
Method reflectionMethod = String.class.getMethod("trim");
Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.unreflect(reflectionMethod);
CallSite callSite = LambdaMetafactory.metafactory(
  // method handle lookup
  lookup,
  // name of the method defined in the target functional interface
  "get",
  // type to be implemented and captured objects
  // in this case the String instance to be trimmed is captured
  MethodType.methodType(Supplier.class, String.class),
  // type erasure, Supplier will return an Object
  MethodType.methodType(Object.class),
  // method handle to transform
  handle,
  // Supplier method real signature (reified)
  // trim accepts no parameters and returns String
  MethodType.methodType(String.class));
Supplier<String> lambda = (Supplier<String>) callSite.getTarget().bindTo(toBeTrimmed).invoke();

That piece of code is not obvious at all so here's a bit more in depth explanation. LambdaMetafactory contains the metafactory postfix for a reason. The metafactory() method does not return the Supplier instance directly as we'd expect. Instead it returns a CallSite object. CallSite.getTarget() returns a method handle which acts as a factory for objects implementing the Supplier interface. So in fact LambdaMetafactory is a factory of factories.

The whole process is divided into two steps: creating the factory and using that factory to instantiate the final object. It directly reflects the process by which lambdas are compiled and executed. Every lambda expression can capture 0 or more values and the CallSite.getTarget method handle represents the capturing process. We can invoke that method handle with any number of values we want to have available in the lambda body.

Static method reference Collections::emptyList doesn't require any external value. Instance method reference stringInstance::trim automatically captures the stringInstance variable. For other cases the number of captured values is equal to the number of external values used within lambda body.

Let's now see the benchmark results.

OpenJDK 8 (Windows 10)
  Score Error
Direct access 3.469 0.196
Reflections 6.505 0.620
Handle 7.183 1.132
Lambda 3.731 0.088

OpenJDK 11.0.6 (Windows 10)
  Score Error
Direct access 3.476 0.329
Reflections 5.786 0.160
Handle 7.309 0.677
Lambda 3.563 0.044

On OpenJDK 11 lambdas created with LambdaMetafactory are almost as fast as direct access itself. On OpenJDK 8 performance is a bit worse but still really good.

Summary

Unsurprisingly normal code beats in terms of performance all other methods of executing code. Whenever you can just go for the simplest solution.

However you can't solve all problems with direct access alone. Frameworks and libraries together with annotations make our life easier. When developing one indirect access is a must and there are several ways of achieving the same goal.

Reflections API is a great starting point. It's well documented and has been around for ages. You'll easily find code examples and libraries filling the base API gaps. While performance is not as good as direct access in many cases it doesn't matter as the overall impact will be low.

If your library often invokes methods indirectly (especially if in tight loops) consider using LambdaMetafactory to transform Method instances into functional interfaces. The code is not that simple but it's certainly worth the trouble.

As for method handles there's no good reason to switch from reflections API unless you're creating your own JVM-based dynamic language where you don't really have a choice due to the nature of the invokedynamic bytecode instruction.

Bibliography

  1. OpenJDK JMH
  2. Baeldung - Invoke dynamic
  3. Infoq - Invoke dynamic - Java's secret weapon
  4. JDK 8 Javadoc - MethodHandle
  5. JDK 8 Javadoc - LambdaMetafactory