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

发表于:2019-8-30 11:34

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:王侦    来源:简书

  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方法的调用。

  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

       上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号