暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

细读源码之JAVA反射方法调用优化

马士兵 2021-07-20
1568

细读源码是《马士兵教育》旗下赏析源码的栏目。我们赏析源码的目的不是为了炫技,而是为了去理解作者的设计思想,并取其精华,去其糟粕,从而写出更加优秀的代码。

另一方面,也可以给面试加分。代码好坏的评价,不可避免地会代入个人的主观色彩,大家和而不同。

在上一篇文章《细读源码之JAVA反射》一文中,我们首先讲解了反射的应用场景以及缺点,其中反射调用一个非常致命的缺点,就是运行效率低下。

为了解决这个问题,JDK高版本对其进行了优化,主要从以下三个方面进行讲解优化过程:

  • 1.反射优化举例
  • 2.反射优化时机参数
  • 3.核心方法解析


一.反射优化举例

Java反射调用,有严重的性能问题,JDK高版本对其进行了优化。本文以JDK1.8为例,我们讲解一下反射调用优化的过程:

通过动态生成class字节码文件,把反射调用过程中的native调用,替换为纯Java方法调用,来提高性能。

由于替换的过程非常耗时,JDK并不是在首次反射调用就直接进行优化,而是通过ReflectionFactory.noInflation和ReflectionFactory.inflationThreshold两个参数来控制替换的时机。

下面举个例子,说明这一过程:

1.测试类定义:

    public class MethodInvoke {
    private void run(String cmd) {
    throw new RuntimeException("exception happen");
    }
    }

    run方法里面仅执行throw Exception的操作,作用是为了打印出调用的堆栈信息,方便后面进行分析。

    2.测试程序:

      package org.example.sourcecode.reflect;
      import java.lang.reflect.Method;


      public class MethodInvokeTest {
      public static void main(String[] args) throws Exception {
      MethodInvoke methodClass = new MethodInvoke();
      Method run = methodClass.getClass().getDeclaredMethod("run", new Class[]{String.class});
      run.setAccessible(true);
      for (int i = 0; i < 2; i++) {
      try {
      run.invoke(methodClass, "delete");
      } catch (Exception e) {
      e.printStackTrace();
      System.err.println();
      }
      }
      }
      }

      执行的时候,需要加上运行参数-Dsun.reflect.inflationThreshold=0,加这个参数的原因,在后面会详细解答。上面代码执行结果如下所示:

        java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.example.sourcecode.reflect.MethodInvokeTest.main(MethodInvokeTest.java:12)
        Caused by: java.lang.RuntimeException: exception happen
        at org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)
        ... 5 more


        java.lang.reflect.InvocationTargetException
        at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.example.sourcecode.reflect.MethodInvokeTest.main(MethodInvokeTest.java:12)
        Caused by: java.lang.RuntimeException: exception happen
        at org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)
        ... 4 more

        分析上面的日志,可以得出下面结论:

        1.第一次反射调用,是通过代理类DelegatingMethodAccessorImpl去调用NativeMethodAccessorImpl.invoke0这个方法,去触发run方法的执行,invoke0是native调用;
        2.在第一次反射调用的时候,该方法反射调用的次数,大于inflationThreshold设置的值0,开始进行反射优化,将DelegatingMethodAccessorImpl的代理对象被替换为动态生成的GeneratedMethodAccessor2
        3.第二次反射调用,调用GeneratedMethodAccessor2.invoke方法,该方法里再去调用run方法,此invoke方法是纯Java调用。
        综上,我们可以得出上面的结论:反射调用的过程中,会把耗时的native调用,替换为纯Java调用,这样JVM可以对字节码进行优化,来提高反射执行的效率。

        二.反射优化时机控制参数

        Java反射优化,通过ReflectionFactory.noInflation和ReflectionFactory.inflationThreshold这两个参数来控制反射优化的时机。

        noInflation参数控制是否在第一次反射调用的时候中就进行优化;noInflation设置为true表示首次反射调用就进行优化,inflationThreshold的值就没有用了;noInflation设置为false表示首次反射调用,不进行优化,待反射方法执行的次数,大于inflationThreshold的设置值后,再进行优化。

        有一些场景,想要禁用反射优化,我们只需要把noInflation设置为false,inflationThreshold设置为Integer.MAX_VALUE即可,就可以达到禁用的效果。

        1.参数定义

        noInflation和inflationThreshold的定义在ReflectionFactory类中,代码如下:

          private static boolean noInflation = false;
          private static int inflationThreshold = 15;

          启动Java程序,如果不增加额外的启动参数,noInflation的默认值为false,inflationThreshold的默认值为15。

          2.参数初始化

            private static void checkInitted() {
            if (!initted) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
            if (System.out == null) {
            return null;
            } else {
            String var1 = System.getProperty("sun.reflect.noInflation");
            if (var1 != null && var1.equals("true")) {
            ReflectionFactory.noInflation = true;
            }
            var1 = System.getProperty("sun.reflect.inflationThreshold");
            if (var1 != null) {
            try {
            ReflectionFactory.inflationThreshold = Integer.parseInt(var1);
            } catch (NumberFormatException var3) {
            throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", var3);
            }
            }
            ReflectionFactory.initted = true;
            return null;
            }
            }
            });
            }
            }

            分析上面代码,我们可以通过-Dsun.reflect.noInflation,来设置noInflation的值,通过-Dsun.reflect.inflationThreshold来设置inflationThreshold的值。

            合理的设置这两个值,可以提高反射的执行效率。对于高频反射执行的方法(设计上应该尽量避免这种发生情况),可以设置noInflation=true,然后通过自动化程序,触发反射执行,达到预热效果,然后再去承载线上正常的流量。

            如果没有预热过程,首次调用时间会很长,影响用户体验。

            3.举例

            还是第一部分的代码,如果我们替换运行参数为-Dsun.reflect.noInflation=true,再次运行,会得到下面的结果:

              java.lang.reflect.InvocationTargetException
              at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
              at java.lang.reflect.Method.invoke(Method.java:498)
              at org.example.sourcecode.reflect.MethodInvokeTest.main(MethodInvokeTest.java:12)
              Caused by: java.lang.RuntimeException: exception happen
              at org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)
              ... 3 more


              java.lang.reflect.InvocationTargetException
              at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
              at java.lang.reflect.Method.invoke(Method.java:498)
              at org.example.sourcecode.reflect.MethodInvokeTest.main(MethodInvokeTest.java:12)
              Caused by: java.lang.RuntimeException: exception happen
              at org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)
              ... 3 more

              运行结果,和参数为-Dsun.reflect.inflationThreshold=0是的结果完全不同。

              noInflation=true的时候,首次调用的时候就直接优化成纯java调用,没有了上面的替换的过程。

              三.核心方法解析

              Java反射的一次调用过程,如下图所示:

              下面对流程图的中的各个方法,做详细说明:

              1.Methed.invoke方法

              invoke方法是反射调用的入口,就从这个方法开始分析,代码如下:

                public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException{
                if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
                }
                }
                MethodAccessor ma = methodAccessor; // read volatile
                if (ma == null) {
                ma = acquireMethodAccessor();
                }
                return ma.invoke(obj, args);
                }

                从上面代码可以看出,Methed.invoke最终执行的是MethodAccessor.invoke方法,MethodAccessor是接口,里面只定义了一个invoke方法。

                此接口一共有3个实现类MethodAccessorImpl,DelegatingMethodAccessorImpl,NativeMethodAccessorImpl,下面我们看一下这3个类的实现。

                2.MethodAccessorImpl

                  package sun.reflect;
                  import java.lang.reflect.InvocationTargetException;


                  abstract class MethodAccessorImpl extends MagicAccessorImpl implements MethodAccessor {
                  MethodAccessorImpl() {
                  }


                  public abstract Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
                  }

                  MethodAccessorImpl是抽象类,内部使用到MethodAccessor接口的地方,都替换成了MethodAccessorImpl,并把可见性设置为包可见,目的是为了收窄权限。

                  3.DelegatingMethodAccessorImpl

                    class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
                    private MethodAccessorImpl delegate;


                    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
                    this.setDelegate(var1);
                    }


                    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
                    return this.delegate.invoke(var1, var2);
                    }


                    void setDelegate(MethodAccessorImpl var1) {
                    this.delegate = var1;
                    }
                    }

                    DelegatingMethodAccessorImpl从命名上就可以知道,该类使用了代理模式,是代理类。

                    真正执行操作的是delegate.invoke方法,delegate是被代理对象,它也继承了MethodAccessorImpl类。

                    运行时,可以通过调用setDelegate去替换被代理对象。

                    反射优化前delegate设置是NativeMethodAccessorImpl实例,优化后delegate就被替换成了动态生成的纯Java调用的版本。

                    4.NativeMethodAccessorImpl

                      class NativeMethodAccessorImpl extends MethodAccessorImpl {
                      private final Method method;
                      private DelegatingMethodAccessorImpl parent;
                      private int numInvocations;


                      NativeMethodAccessorImpl(Method var1) {
                      this.method = var1;
                      }


                      public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
                      if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
                      MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
                      this.parent.setDelegate(var3);
                      }


                      return invoke0(this.method, var1, var2);
                      }


                      void setParent(DelegatingMethodAccessorImpl var1) {
                      this.parent = var1;
                      }


                      private static native Object invoke0(Method var0, Object var1, Object[] var2);
                      }

                      NativeMethodAccessorImpl是反射native调用的版本,numInvocations字段是计数器,记录反射已经被调用的次数,当++this.numInvocations > ReflectionFactory.inflationThreshold时,启用优化机制,调用MethodAccessorGenerator.generateMethod生成一个动态类,调用DelegatingMethodAccessorImpl.setDelegate进行实现方式替换。

                      5.GeneratedMethodAccessor

                      GeneratedMethodAccessor类是运行时调用MethodAccessorGenerator.generateMethod动态生成的,用来替换NativeMethodAccessorImpl实现,通过反编译查看,GeneratedMethodAccessor类定义如下:

                        package sun.reflect;


                        import java.lang.reflect.InvocationTargetException;
                        import org.example.sourcecode.reflect.MethodInvoke;


                        public class GeneratedMethodAccessor2 extends MethodAccessorImpl {
                        public GeneratedMethodAccessor2() {}


                        public Object invoke(Object var1, Object[] var2) throws InvocationTargetException {
                        if (var1 == null) {
                        throw new NullPointerException();
                        } else {
                        MethodInvoke var10000;
                        String var10001;
                        try {
                        var10000 = (MethodInvoke)var1;
                        if (var2.length != 1) {
                        throw new IllegalArgumentException();
                        }


                        var10001 = (String)var2[0];
                        } catch (NullPointerException | ClassCastException var4) {
                        throw new IllegalArgumentException(var4.toString());
                        }


                        try {
                        var10000.run(var10001);
                        return null;
                        } catch (Throwable var3) {
                        throw new InvocationTargetException(var3);
                        }
                        }
                        }
                        }

                        GeneratedMethodAccessor2继承了MethodAccessorImpl并实现了invoke方法,invoke实现非常简单,直接对传入的Object进行强制类型转换为目标类型,然后显示地执行其方法,在此示例中,是直接调用的run方法,实现了纯java调用。

                        6.MethodAccessorGenerator.generateMethod

                        generateMethod方法非常长,摘取部分代码如下:

                          private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {
                          ByteVector var10 = ByteVectorFactory.create();
                          this.asm = new ClassFileAssembler(var10);
                          //……
                          this.asm.emitMagicAndVersion();
                          //……
                          if (this.asm.cpi() != var11) {
                          throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")");
                          } else {
                          final byte[] var17 = var10.getData();
                          return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {
                          public MagicAccessorImpl run() {
                          try {
                          return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
                          } catch (IllegalAccessException | InstantiationException var2) {
                          throw new InternalError(var2);
                          }
                          }
                          });
                          }
                          }

                          其中this.asm.emitMagicAndVersion()代码如下:

                            publicvoid emitMagicAndVersion() {
                            this.emitInt(-889275714);
                            this.emitShort((short)0);
                            this.emitShort((short)49);
                            }

                            又看到了-889275714这个熟悉的数字,十六进制表示是cafebabe,Java Class文件的魔数,上次看到这个数字是讲动态代理类生成的时候遇到的。

                            反射优化的动态类也是按照class file的标准,直接操作二进制数据生成的,然后调用ClassDefiner.defineClass进行类加载,最后通过newInstance的方法创建其一个实例。

                            7.ReflectionFactory.acquireMethodAccessor

                            在入口方法Methed.invoke中MethodAccessor对象,是通过调用acquireMethodAccessor来获取的,代码如下:

                              private MethodAccessor acquireMethodAccessor() {
                              // First check to see if one has been created yet, and take it
                              // if so
                              MethodAccessor tmp = null;
                              if (root != null) tmp = root.getMethodAccessor();
                              if (tmp != null) {
                              methodAccessor = tmp;
                              } else {
                              // Otherwise fabricate one and propagate it up to the root
                              tmp = reflectionFactory.newMethodAccessor(this);
                              setMethodAccessor(tmp);
                              }
                              return tmp;
                              }

                              要想读懂这个方法,首先要了解Method对象的基本构成。每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。

                              当我们调用getMethod等方法来获取Method对象时,我们获得的Method对象都是root对象的副本。

                              root对象持有一个MethodAccessor对象,所有获取到的Method对象都共享这一个MethodAccessor对象,所以开始的时候会执行root.getMethodAccessor来获取。

                              如果获取的对象为null,表示该方法还没有被反射调用过,调用newMethodAccessor进行methodAccessor初始化。

                              8.ReflectionFactory.newMethodAccessor

                                public MethodAccessor newMethodAccessor(Method var1) {
                                checkInitted();
                                if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
                                return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
                                } else {
                                NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
                                DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
                                var2.setParent(var3);
                                return var3;
                                }
                                }

                                newMethodAccessor中使用了noInflation配置,当设置为true时,首次反射调用就进行优化,动态生成纯Java版本的调用。

                                设置为false的时候,则使用native版本。

                                到此,Java反射优化的内容就讲完了,下一章讲解跟反射调用优化导致的线上事故,敬请期待。

                                文章转载自马士兵,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                评论