细读源码是《马士兵教育》旗下赏析源码的栏目。我们赏析源码的目的不是为了炫技,而是为了去理解作者的设计思想,并取其精华,去其糟粕,从而写出更加优秀的代码。
另一方面,也可以给面试加分。代码好坏的评价,不可避免地会代入个人的主观色彩,大家和而不同。
在上一篇文章《细读源码之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.InvocationTargetExceptionat 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 happenat org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)... 5 morejava.lang.reflect.InvocationTargetExceptionat 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 happenat org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)... 4 more
分析上面的日志,可以得出下面结论:
二.反射优化时机控制参数
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.InvocationTargetExceptionat 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 happenat org.example.sourcecode.reflect.MethodInvoke.run(MethodInvoke.java:5)... 3 morejava.lang.reflect.InvocationTargetExceptionat 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 happenat 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 volatileif (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 soMethodAccessor tmp = null;if (root != null) tmp = root.getMethodAccessor();if (tmp != null) {methodAccessor = tmp;} else {// Otherwise fabricate one and propagate it up to the roottmp = 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反射优化的内容就讲完了,下一章讲解跟反射调用优化导致的线上事故,敬请期待。




