反射调用简单性能测试与调优

参考Lambda、MethodHandle、CallSite调用简单性能测试与调优

1.基准版本

import java.lang.reflect.Method;
public class ReflectionPerTest1 {

    public static void target(int i) {
        // 空方法
    }

    public static void main(String[] args) throws Exception {
        Class klass = Class.forName("com.enjoy.learn.core.oop.reflection.ReflectionPerTest1");
        Method method = klass.getMethod("target", int.class);

        long current = System.currentTimeMillis();
        
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, 128);
        }
    }

}
305
304
304
318
313

查看其字节码,可以看到有两项额外的操作:
1)128自动装箱为Integer类型
2)生成一个Object数组,并存储传入参数
这两个操作除了带来性能开销外,还可能占用堆内存,使得GC更加频繁。(-XX:+PrintGC)

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=6, locals=8, args_size=1
         0: ldc           #2                  // String com.enjoy.learn.core.oop.reflection.ReflectionPerTest1
         2: invokestatic  #3                  // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
         5: astore_1
         6: aload_1
         7: ldc           #4                  // String target
         9: iconst_1
        10: anewarray     #5                  // class java/lang/Class
        13: dup
        14: iconst_0
        15: getstatic     #6                  // Field java/lang/Integer.TYPE:Ljava/lang/Class;
        18: aastore
        19: invokevirtual #7                  // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
        22: astore_2
        23: invokestatic  #8                  // Method java/lang/System.currentTimeMillis:()J
        26: lstore_3
        27: iconst_1
        28: istore        5
        30: iload         5
        32: ldc           #9                  // int 2000000000
        34: if_icmpgt     88
        37: iload         5
        39: ldc           #10                 // int 100000000
        41: irem
        42: ifne          63
        45: invokestatic  #8                  // Method java/lang/System.currentTimeMillis:()J
        48: lstore        6
        50: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
        53: lload         6
        55: lload_3
        56: lsub
        57: invokevirtual #12                 // Method java/io/PrintStream.println:(J)V
        60: lload         6
        62: lstore_3
        63: aload_2
        64: aconst_null
        65: iconst_1
        66: anewarray     #13                 // class java/lang/Object
        69: dup
        70: iconst_0
        71: sipush        128
        74: invokestatic  #14                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        77: aastore
        78: invokevirtual #15                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        81: pop
        82: iinc          5, 1
        85: goto          30
        88: return

2.避免自动装箱

        Integer I128 = Integer.valueOf(128);
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, I128);
        }
        78: iconst_0
        79: aload         5
        81: aastore
        82: invokevirtual #15                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;


      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           58      13     7  temp   J
           38      54     6     i   I
            0      93     0  args   [Ljava/lang/String;
            6      87     1 klass   Ljava/lang/Class;
           23      70     2 method   Ljava/lang/reflect/Method;
           27      66     3 current   J
           35      58     5  I128   Ljava/lang/Integer;
192
195
190
189
190

Java缓存[-128,127]所有整数所定义的Integer对象,可以将范围扩大至覆盖128.
-Djava.lang.Integer.IntegerCache.high=128

        78: iconst_0
        79: sipush        128
        82: invokestatic  #9                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        85: aastore
        86: invokevirtual #15                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
216
217
217
218
217

3.在循环外新建一个Object数组,避免频繁GC

但也有可能发生性能变坏,因为当对象不逃逸时,JIT可以选择栈分配甚至虚拟分配,不占用堆空间。
在堆外建立数组时,无法优化掉访问数组的操作。

        Object[] argArray = new Object[1];
        argArray[0] = 128;
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, argArray);
        }
245
241
243
244
243

4.关闭反射调用的Inflation机制和权限检查

从而取消委派实现,直接使用动态实现。
此外,每次反射调用都会检查目标方法的权限,关闭掉。

-Dsun.reflect.noInflation=true
        method.setAccessible(true);  // 关闭权限检查

        Integer I128 = Integer.valueOf(128);
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, I128);
        }
123
124
123
125
122

5.内联瓶颈——Method.invoke

变快的原因是JIT中的方法内联。在关闭Inflation的情况下,内联的瓶颈在于Method.invoke方法中对MethodAccessor.invoke方法的调用。


反射调用简单性能测试与调优_第1张图片

JVM无法同时记录多个类的关于调用点的类型profile,因此可能造成所测试的反射调用没有被内联。
类型profile:对于invokevirtula或者invokeinterface,JVM会记录下调用者的具体类型。

public class ReflectionPerTest1 {

    public static void target(int i) {
        // 空方法
    }

    public static void main(String[] args) throws Exception {
        Class klass = Class.forName("com.enjoy.learn.core.oop.reflection.ReflectionPerTest1");
        Method method = klass.getMethod("target", int.class);
        method.setAccessible(true);  // 关闭权限检查
        polluteProfile();

        long current = System.currentTimeMillis();
        Integer I128 = Integer.valueOf(128);
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            method.invoke(null, 128);
        }
    }

    public static void polluteProfile() throws Exception {
        Method method1 = ReflectionPerTest1.class.getMethod("target1", int.class);
        Method method2 = ReflectionPerTest1.class.getMethod("target2", int.class);
        for (int i = 0; i < 2000; i++) {
            method1.invoke(null, 0);
            method2.invoke(null, 0);
        }
    }
    public static void target1(int i) { }
    public static void target2(int i) { }

}

1047
1024
1021
1030
1042

只要干扰了Method.invoke方法的类型profile,性能开销会急剧上升。
提高JVM关于每个调用能够记录的类型数目(-xx:TypeProfileWidth=3默认值为2)。测试结果无变化。

6.多次获取的Method是同一个对象

    public static void polluteProfile() throws Exception {
        Method method1 = ReflectionPerTest1.class.getMethod("target", int.class);
        Method method2 = ReflectionPerTest1.class.getMethod("target", int.class);
        System.out.println(method1.equals(method2));
        for (int i = 0; i < 2000; i++) {
            method1.invoke(null, 0);
            method2.invoke(null, 0);
        }
    }
true
...
300
302
309
307
305

你可能感兴趣的:(反射调用简单性能测试与调优)