Hello,

it seems like Method.getParameterTypes() is often misused in JDK (and beyond): 
array returned from the method is used only to acquire number of method params 
by retrieving array.length.
The problem here is that Method.getPatameterTypes() clones underlying array and 
returns the copy.
Instead we can use Method.getParameterCount() which doesn't allocate any 
additional memory but returns directly the length of underlying  array.

To measure probable performance difference I've created a benchmark for the 
most simple case when tested method has no parameters:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MethodToStringBenchmark {
  private Method method;

  @Setup
  public void setup() throws Exception { method = 
getClass().getMethod("toString"); }

  @Benchmark
  public int getParameterCount() { return method.getParameterCount(); }

  @Benchmark
  public int getParameterTypes() { return method.getParameterTypes().length; }
}

on my i7-7700 with JDK 11 it produces these results:


Benchmark                                                               Mode  
Cnt     Score    Error   Units

MethodToStringBenchmark.getParameterCount                               avgt   
25     2,528 ±  0,085   ns/op
MethodToStringBenchmark.getParameterCount:·gc.alloc.rate                avgt   
25    ≈ 10⁻⁴           MB/sec
MethodToStringBenchmark.getParameterCount:·gc.alloc.rate.norm           avgt   
25    ≈ 10⁻⁷             B/op
MethodToStringBenchmark.getParameterCount:·gc.count                     avgt   
25       ≈ 0           counts

MethodToStringBenchmark.getParameterTypes                               avgt   
25     7,299 ±  0,410   ns/op
MethodToStringBenchmark.getParameterTypes:·gc.alloc.rate                avgt   
25  1999,454 ± 89,929  MB/sec
MethodToStringBenchmark.getParameterTypes:·gc.alloc.rate.norm           avgt   
25    16,000 ±  0,001    B/op
MethodToStringBenchmark.getParameterTypes:·gc.churn.G1_Eden_Space       avgt   
25  2003,360 ± 91,537  MB/sec
MethodToStringBenchmark.getParameterTypes:·gc.churn.G1_Eden_Space.norm  avgt   
25    16,030 ±  0,045    B/op
MethodToStringBenchmark.getParameterTypes:·gc.churn.G1_Old_Gen          avgt   
25     0,004 ±  0,001  MB/sec
MethodToStringBenchmark.getParameterTypes:·gc.churn.G1_Old_Gen.norm     avgt   
25    ≈ 10⁻⁵             B/op
MethodToStringBenchmark.getParameterTypes:·gc.count                     avgt   
25  2380,000           counts
MethodToStringBenchmark.getParameterTypes:·gc.time                      avgt   
25  1325,000               ms

I've prepared a small patch to replace usage of getParameterTypes() with 
getParameterCount() where appropriate in java.base module.

The patch is attached.

Regards,
Sergey Tsypanov
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
@@ -281,13 +281,13 @@
         switch (m.getName()) {
         case "toString":
             return (m.getReturnType() == String.class
-                    && m.getParameterTypes().length == 0);
+                    && m.getParameterCount() == 0);
         case "hashCode":
             return (m.getReturnType() == int.class
-                    && m.getParameterTypes().length == 0);
+                    && m.getParameterCount() == 0);
         case "equals":
             return (m.getReturnType() == boolean.class
-                    && m.getParameterTypes().length == 1
+                    && m.getParameterCount() == 1
                     && m.getParameterTypes()[0] == Object.class);
         }
         return false;
diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java
--- a/src/java.base/share/classes/java/lang/reflect/Executable.java
+++ b/src/java.base/share/classes/java/lang/reflect/Executable.java
@@ -378,7 +378,7 @@
     private void verifyParameters(final Parameter[] parameters) {
         final int mask = Modifier.FINAL | Modifier.SYNTHETIC | Modifier.MANDATED;
 
-        if (getParameterTypes().length != parameters.length)
+        if (getParameterCount() != parameters.length)
             throw new MalformedParametersException("Wrong number of parameters in MethodParameters attribute");
 
         for (Parameter parameter : parameters) {
diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
--- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
+++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
@@ -593,10 +593,10 @@
                 Constructor<?> noArgCtor = null;
                 // public ctors only
                 for (Constructor<?> c : ex.getClass().getConstructors()) {
-                    Class<?>[] ps = c.getParameterTypes();
-                    if (ps.length == 0)
+                    int parameterCount = c.getParameterCount();
+                    if (parameterCount == 0)
                         noArgCtor = c;
-                    else if (ps.length == 1 && ps[0] == Throwable.class)
+                    else if (parameterCount == 1 && c.getParameterTypes()[0] == Throwable.class)
                         return (Throwable)c.newInstance(ex);
                 }
                 if (noArgCtor != null) {
diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotationType.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotationType.java
--- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationType.java
+++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationType.java
@@ -121,7 +121,7 @@
             if (Modifier.isPublic(method.getModifiers()) &&
                 Modifier.isAbstract(method.getModifiers()) &&
                 !method.isSynthetic()) {
-                if (method.getParameterTypes().length != 0) {
+                if (method.getParameterCount() != 0) {
                     throw new IllegalArgumentException(method + " has params");
                 }
                 String name = method.getName();

Reply via email to