JDK-8185348 : Major performance regression in GetMethodDeclaringClass and other JVMTI Method functions
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 8u141
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2017-07-26
  • Updated: 2020-12-07
  • Resolved: 2020-08-27
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 8
8u281 b01Fixed
Related Reports
Relates :  
Description
Starting from JDK 8u112 certain JVMTI functions became extremely slow. All these functions accept jmethodID as an argument.

- GetMethodName
- GetMethodDeclaringClass
- GetMethodModifiers
- GetLineNumberTable
- GetBytecodes

etc.

The attached code demonstrates the problem. This code executes GetMethodDeclaringClass one million times. It works 86 ms on JDK 8u102, but it takes 360'000 ms on JDK 8u141.

The problem appeared after JDK-8147451. The fix introduced a validity check Method::is_method_id for each translation of jmethodID argument. This involves a linear search among all jmethodIDs. Various tools like profilers allocate jmethodIDs for many loaded classes, so that the total number of jmethodIDs may reach hundreds of thousands. Linear search is not going to work in such cases.
Comments
I'm looking at the changeset of DK-8147451 for a root cause of this regression. These two change fragments are not present in 14: @@ -1846,6 +1846,9 @@ Method* m = resolve_jmethod_id(mid); assert(m != NULL, "should be called with non-null method"); InstanceKlass* ik = m->method_holder(); + if (ik == NULL) { + return false; + } ClassLoaderData* cld = ik->class_loader_data(); if (cld->jmethod_ids() == NULL) return false; return (cld->jmethod_ids()->contains((Method**)mid)); @@ -1853,6 +1856,9 @@ Method* Method::checked_resolve_jmethod_id(jmethodID mid) { if (mid == NULL) return NULL; + if (!Method::is_method_id(mid)) { + return NULL; + } Method* o = resolve_jmethod_id(mid); if (o == NULL || o == JNIMethodBlock::_free_method || !((Metadata*)o)->is_method()) { return NULL; It does not look that the first one can be a root cause of this regression. The only candidate for root cause is the second fragment. For comparison, the 14 version is: Method* Method::checked_resolve_jmethod_id(jmethodID mid) { if (mid == NULL) return NULL; Method* o = resolve_jmethod_id(mid); if (o == NULL || o == JNIMethodBlock::_free_method || !((Metadata*)o)->is_method()) { return NULL; } return o; }; The implementation of the function is_method_id() is: bool Method::is_method_id(jmethodID mid) { Method* m = resolve_jmethod_id(mid); assert(m != NULL, "should be called with non-null method"); InstanceKlass* ik = m->method_holder(); ClassLoaderData* cld = ik->class_loader_data(); if (cld->jmethod_ids() == NULL) return false; return (cld->jmethod_ids()->contains((Method**)mid)); } I think, the call to is_method_id() is pretty expensive because of the call to jmethod_ids()->contains(). So that, it would be nice to prove if removing this fragment really fixes the issue in 8u: + if (!Method::is_method_id(mid)) { + return NULL; + } It seems that the function checked_resolve_jmethod_id() is called only from jniCheck::validate_jmethod_id() (the uses in whitebox.cpp are not important). However, the calls to this function are generated by the build/linux-x86_64-server-*/hotspot/variant-server/gensrc/jvmtifiles/jvmtiEnter.cpp. Please, see this fragment: prims/jvmtiEnter.xsl: <xsl:text> Method* method_oop = Method::checked_resolve_jmethod_id(</xsl:text> There are 14 JVMTI function wrappers making calls to the checked_resolve_jmethod_id() with the fragments like this one: Method* method_oop = Method::checked_resolve_jmethod_id(method); if (method_oop == NULL) { return JVMTI_ERROR_INVALID_METHODID; } These checks are in all methods accepting jmethodID arguments: GetMethodDeclaringClass, GetMethodModifiers, GetMaxLocals, GetArgumentsSize, GetLineNumberTable, GetMethodLocation, GetLocalVariableTable, GetBytecodes, IsMethodNative, IsMethodSynthetic, IsMethodObsolete, SetBreakpoint, ClearBreakpoint So, my suggestion is to move this check: + if (!Method::is_method_id(mid)) { + return NULL; + } from checked_resolve_jmethod_id() back to the jniCheck::validate_jmethod_id(). I hope, it is going to fix this performance regression.
05-09-2019

Thanks, Daniil! My assumption that this issue is present in both 9 and 10 is wrong. The bug JDK-8147451 was only fixed in the 8u and never forward ported to 9 and 10. Also, this bug has the 9-na label. So that I'm removing 9 and 10 from the "Affects versions/s" list.
04-09-2019

Removed Java 11 from affected versions.
17-08-2019

I tried the attached use case ( slightly modified in the way it finds classes to load, since there is no rt.jar in JDK9+) with different builds. With about 6,200 classes and 185,000 methods being loaded below are the benchmarks for the different JDKs. JDK 8u102 - 8ms JDK 8u221 - 4747 ms JDK 11.0.4 - 12 ms JDK 13 - 13 ms JDK 14 - 12 ms Thus the issue seems to be only applicable to JDK 8.
17-08-2019

Removed 11 target. Shafi to work, and we can fix in (8), 11 or later as appropriate. TBD
13-04-2018

I agree, we introduced a performance regression in the fix for JDK-8147451. I was incorrect in that email discussion saying that the check_resolve_jmethod_id() is called only from the jniCheck context and from the Whitebox API. I missed it is also called in the jvmtiEnter.cpp that is generated with the jvmtiEnter.xsl. So that it is better to fix this regression. The problem must be also present in both jdk 9 and 10, I think. Targeted this issue to 11 for now. Please, let me know if it is better to fix this in 8 first.
02-01-2018

Please ensure the bug being blamed for this regression is actually linked to this bug report - which it is now. Thanks.
01-08-2017

I've found a discussion on this change: http://mail.openjdk.java.net/pipermail/hotspot-runtime-dev/2016-June/019903.html where it is claimed that check_resolve_jmethod_id() is not supposed to be lightweight since it is called only from jniCheck context and from Whitebox API. However, this is not true. check_resolve_jmethod_id is also used to translate jmethodID arguments of JVM TI functions to Method*. See jvmtiEnter.xsl. Looks like this usage was missed since the source code is generated using XSLT.
26-07-2017