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

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

为了解决这个问题,JDK高版本对其进行了优化

一.反射优化举例

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

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

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

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

1.测试类定义:

细读源码之JAVA反射方法调用优化_第1张图片

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

2.测试程序:

细读源码之JAVA反射方法调用优化_第2张图片

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

细读源码之JAVA反射方法调用优化_第3张图片 

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

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类中,代码如下:

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

2.参数初始化

细读源码之JAVA反射方法调用优化_第4张图片

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

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

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

3.举例

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

细读源码之JAVA反射方法调用优化_第5张图片

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

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

三.核心方法解析

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

细读源码之JAVA反射方法调用优化_第6张图片

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

1.Methed.invoke方法

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

细读源码之JAVA反射方法调用优化_第7张图片

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

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

2.MethodAccessorImpl

细读源码之JAVA反射方法调用优化_第8张图片

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

3.DelegatingMethodAccessorImpl

细读源码之JAVA反射方法调用优化_第9张图片

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

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

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

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

4.NativeMethodAccessorImpl

细读源码之JAVA反射方法调用优化_第10张图片

 细读源码之JAVA反射方法调用优化_第11张图片

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

5.GeneratedMethodAccessor

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

细读源码之JAVA反射方法调用优化_第12张图片

 细读源码之JAVA反射方法调用优化_第13张图片

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

6.MethodAccessorGenerator.generateMethod

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

细读源码之JAVA反射方法调用优化_第14张图片

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

细读源码之JAVA反射方法调用优化_第15张图片

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

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

7.ReflectionFactory.acquireMethodAccessor

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

细读源码之JAVA反射方法调用优化_第16张图片

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

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

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

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

8.ReflectionFactory.newMethodAccessor

 细读源码之JAVA反射方法调用优化_第17张图片

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

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

到此,Java反射优化的内容就讲完了

你可能感兴趣的:(java,开发语言,后端)