JDK-8061950 : Class.getMethods() exhibits quadratic time complexity
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8u20,9
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2014-10-23
  • Updated: 2017-01-05
  • Resolved: 2017-01-03
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 9
9 b151Fixed
Related Reports
Blocks :  
Relates :  
Relates :  
Description
As originally reported by Martin Buchholz here: 
 http://mail.openjdk.java.net/pipermail/core-libs-dev/2014-October/029272.html 

The profile shows the hot call tree sub-branch here: 

  |  |  +-  15.981   (18%)    java.lang.Class.getMethods()
  |  |  |  +-  15.971   (18%)    java.lang.Class.privateGetPublicMethods()
  |  |  |  |  +-  15.791   (18%)    java.lang.Class$MethodArray.removeByNameAndDescriptor(java.lang.reflect.Method)
  |  |  |  |  |  +-  10.687   (12%)    java.lang.Class$MethodArray.matchesNameAndDescriptor(java.lang.reflect.Method, java.lang.reflect.Method)
  |  |  |  |  |  |  +-  0.020   (0%)    java.lang.reflect.Method.getParameterTypes()
  |  |  |  |  |  |    +-  0.010   (0%)    java.lang.Object.clone()
  |  |  |  |  |  +-  0.020   (0%)    java.lang.Class$MethodArray.remove(int)
  |  |  |  |  +-  0.100   (0%)    java.lang.Class.privateGetDeclaredMethods(boolean)
  |  |  |  |  +-  0.050   (0%)    java.lang.Class.privateGetPublicMethods()
  |  |  |  |  +-  0.020   (0%)    java.lang.Class$MethodArray.addAllIfNotPresent(java.lang.Class$MethodArray)

This is what happens. Class.getMethods() ends up calling 

    private Method[] privateGetPublicMethods() {
       ...

                MethodArray supers = new MethodArray();
                supers.addAll(c.privateGetPublicMethods());
                // Filter out concrete implementations of any
                // interface methods
                for (int i = 0; i < supers.length(); i++) {
                    Method m = supers.get(i);
                    if (m != null &&
                            !Modifier.isAbstract(m.getModifiers()) &&
                            !m.isDefault()) {
                        inheritedMethods.removeByNameAndDescriptor(m);
                    }
                }

       ....

       // Filter out all local methods from inherited ones
        for (int i = 0; i < methods.length(); i++) {
            Method m = methods.get(i);
            inheritedMethods.removeByNameAndDescriptor(m);
        }

...which tries to filter out the inherited methods for the current class. Unfortunately, the call to removeByNameAndDescriptor yields *another* loop of "inherited" methods:

        void removeByNameAndDescriptor(Method toRemove) {
            for (int i = 0; i < length; i++) {
                Method m = methods[i];
                if (m != null && matchesNameAndDescriptor(m, toRemove)) {
                    remove(i);
                }
            }
        }

...and this yields quadratic complexity. We should try to do better lookups instead of linear searches here.

Comments
Re-application of fix for this bug is tracked by JDK-8172190
03-01-2017

Reopened because the change was backed out to resolve test failures
26-12-2016

New patch that is hopefully easier to grasp is here: http://cr.openjdk.java.net/~plevart/jdk9-dev/Class.getMethods.new/webrev.04/
04-10-2016

Latest patch is this: http://cr.openjdk.java.net/~plevart/jdk9-dev/Class.getMethods/webrev.09/
01-04-2015