Project

General

Profile

Feature #11004

Optimizing Java Reflection Method.invoke

Added by Teodor Gorghe 7 months ago. Updated 7 months ago.

Status:
New
Priority:
Normal
Assignee:
-
Target version:
-
Start date:
Due date:
% Done:

0%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
reviewer:
production:
No
env_name:
topics:

Related issues

Related to Runtime Infrastructure - Bug #10071: Method code too large - ReflectASM Closed
Related to Runtime Infrastructure - Feature #9686: move FWD to Java 25 WIP
Related to Runtime Infrastructure - Feature #9685: move FWD to Java 21 WIP
Related to Base Language - Feature #6819: refactor FWD proxy implementation to use ReflectASM instead of Java Method reflection New

History

#1 Updated by Teodor Gorghe 7 months ago

I have done some testing to benchmarks to conclude some testing which includes Java Reflection invocation scenarios and I have come with the following conclusion:
- For a 4GL internal procedure, when called 100000 times, it takes 72 milliseconds. For the same scenario in FWD, the time goes down to 50 milliseconds.
- When calling external procedures 1000 times, 4GL takes 352 ms and FWD takes around 8 ms.

Even that this shows that there are no issues, there are some scenarios when FWD uses reflection method invocation for stuff like static dataset proxy, field reference, etc, which these scenarios are slower than 4GL.

I have done some tests with Java 17 vs Java 21 and Java 25, having FWD compiled with Java 17/21/25 and I have found the following:
  • On OpenJDK 17, calling Method.setAccessible(true) when it is not necessary, has a performance impact.
    I have made a simple Java Application which illustrates how fast is each reflection invocation method.
    When calling a method from the same instance, 1000000 times, Java direct call takes 4375692 ns and Java Method invoke API takes 7948120 ns (without calling setAccessible(true) every time).
    When using setAccessible for every call, the method invocation takes 9676134 ns.
    Even that this shows almost Method.invoke is 3 times slower with setAccessible call, it is actually faster than using ReflectASM, which was being removed from #10071.
    The -Dsun.reflect.inflationThreshold=0 JVM flag has no effect in this case because the Method.invoke moves to a faster call after 15th run.
    The Class.getDeclaredMethod operation is also an expensive operation which needs to be addressed.
  • On OpenJDK 18+, the standard Reflection invoke call has being replaced internally with MethodHandles API, which proves that in the simple Java application, it is almost 5 times slower (23355147 ns). I don't know if there is an alternative, but this indicates to us that we need to move to an alternative API (using MethodHandles directly, or enforce using the old API using JVM args).

#2 Updated by Teodor Gorghe 7 months ago

  • Related to Bug #10071: Method code too large - ReflectASM added

#3 Updated by Alexandru Lungu 7 months ago

#4 Updated by Alexandru Lungu 7 months ago

#5 Updated by Greg Shah 7 months ago

  • Related to Feature #6819: refactor FWD proxy implementation to use ReflectASM instead of Java Method reflection added

#6 Updated by Greg Shah 7 months ago

Make sure to read #6819, we did some early work there and I completely agree that using MethodHandle is a really good target.

#7 Updated by Tomasz Domin 7 months ago

Would introducing Java Modularity affect those benchmarks ? There could be some addition access checks.

#8 Updated by Teodor Gorghe 7 months ago

Greg Shah wrote:

Make sure to read #6819, we did some early work there and I completely agree that using MethodHandle is a really good target.

This is a very good observation.
I have done some tests on a customer application and I have noticed that the execution time for a test suite JDK 17 vs JDK 25 is around the same, with a decrease of 1% on JDK 25. I can't draw a conclusion because most of the time is spent on some operations that is not related to Method.Invoke. The memory usage has been decreased because of the new -XX:+UseCompactObjectHeaders feature. The application startup is also much faster. Note that this testing was done with the old JDK17 bytecode and JVM args which meant to be for JDK 17.

On #6819, I see that there were some attempts on reducing CFO.invoke calls by using lambda methods, which from my perspective, is the fastest. JIT Compiler knows the best how to inline it, making it similar to a direct call.

The benchmarks from #11004-1 were some confirmation tests to confirm what I have read from internet, with a simplified scenario, calling an accessible public method from an instance. I think adding Java Modularity will affect these benchmarks, but both Invocation APIs are affected in the same way in JDK 21+.

Also, starting with JDK 22+, the old Reflection API implementation was completely removed, so, in order to support JDK 25, we can't just use -Djdk.reflect.useDirectMethodHandle=false.

Also available in: Atom PDF