JDK-8249719 : MethodHandle performance suffers from bad ResolvedMethodTable hash function
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 11,14,16
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2020-07-19
  • Updated: 2021-04-07
  • Resolved: 2020-07-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 16
11.0.12-oracleFixed 16 b08Fixed
Related Reports
Relates :  
Description
A severe performance regression was noticed when migrating an application from JDK 8 to JDK 14. This application runs lots of JS scripts under Nashorn in multiple threads.

The problem was narrowed down to ResolvedMethodTable. async-profiler showed that all worker threads were busy almost all time inside java_lang_invoke_ResolvedMethodName::find_resolved_method (see attached profile.svg).

ANALYSIS

With -Xlog:membername*=trace, JVM log was full of messages like

ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c7112440.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c70c9840.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c7136840.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c70be440.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c710bc40.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c7074840.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
ResolvedMethod entry added for java.lang.invoke.LambdaForm$MH/0x00000017c7064840.invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

It is also remarkable that resizing table from 16384 to 32768 buckets took 6 minutes, and during this time the JVM did not respond at all.

[6.718s][debug][membername,table,perf] Grow, 0.0016585 secs
[6.718s][info ][membername,table     ] Grown to size:2048

[280.415s][debug][membername,table,perf] Grow, 0.0034216 secs
[280.415s][info ][membername,table     ] Grown to size:4096

[665.700s][debug][membername,table,perf] Grow, 0.0132153 secs
[665.700s][info ][membername,table     ] Grown to size:8192

[909.242s][debug][membername,table,perf] Grow, 1.3430992 secs
[909.242s][info ][membername,table     ] Grown to size:16384

[1781.645s][debug][membername,table,perf] Grow, 396.2095231 secs
[1781.668s][info ][membername,table     ] Grown to size:32768

I made a simple program that demonstrates the problem. See attached SlowMethodHandles.java

On JDK 8, this program easily creates 1 million MethodHandles, while on JDK 14 it almost dies after 30K handles.

The reason is the way how ResolvedMethodTable computes Method hashes:

unsigned int method_hash(const Method* method) {
  unsigned int name_hash = method->name()->identity_hash();
  unsigned int signature_hash = method->signature()->identity_hash();
  return name_hash ^ signature_hash;
}

Obviously, for the above invoke() methods of generated LambdaForms, all hashes are the same, since all methods have the same name and the same signature.

SOLUTION

method_hash() in resolvedMethodTable.cpp needs to be modified to take the holder class into account.

It's worth mentioning that Method::log_touched() also calculates Method hash, but includes the class:

  unsigned int hash = my_class->identity_hash() +
                      my_name->identity_hash() +
                      my_sig->identity_hash();

To fix the issue, I rewrote method_hash() as shown below, and the application became fast again.

unsigned int method_hash(const Method* method) {
  unsigned int hash = method->klass_name()->identity_hash();
  hash = (hash * 31) ^ method->name()->identity_hash();
  hash = (hash * 31) ^ method->signature()->identity_hash();
  return hash;
}

Comments
Fix Request (11u) This significantly improves performance under heavy MH load. Patch applies cleanly to 11u. Manual test improves from 10 minutes to just 2 minutes after the fix. Patched JDK passes tier1.
01-04-2021

URL: https://hg.openjdk.java.net/jdk/jdk/rev/3f45d3ae36b6 User: simonis Date: 2020-07-29 06:33:12 +0000
29-07-2020