反射的实现原理
Java的反射机制允许我们运行时动态地调用某个对象的方法、新建对象实例、获取对象的属性等。
Connected to the target VM, address: '127.0.0.1:49230', transport: 'socket'
java.lang.Exception: test#0
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#1
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#2
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#3
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#4
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#5
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#6
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#7
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#8
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#9
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#10
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#11
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#12
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#13
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#14
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#15
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
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 com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#16
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#17
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#18
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#19
at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
Disconnected from the target VM, address: '127.0.0.1:49230', transport: 'socket'
Process finished with exit code 0
发现第16次之后,就没有了invoke0()方法的调用.
同一段代码,运行的堆栈结果与执行次数有关,在第0~15次时调用方式为sun.reflect.NativeMethodAccessorImpl.invoke0,从第16次开始调用方式变为了sun.reflect.GeneratedMethodAccessor1.invoke。
反射方法源码分析
Method.invoke方法调用了MethodAccessor.invoke方法,MethodAccessor是一个接口,它的源码如下所示。
public interface MethodAccessor {
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}
从输出的堆栈可以看到MethodAccessor的实现类是委托类DelegatingMethodAccessorI-mpl,它的invoke方法非常简单,就是把调用委托给了真正的MethodAccessorImpl实现类。
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
return delegate.invoke(obj, args);
}
通过堆栈可以看到在第0~15次调用中,实现类是NativeMethodAccessorImpl,从第16次调用开始,实现类是GeneratedMethodAccessor1,玄机就在NativeMethodAccessor-Impl的invoke方法中,如图3-18所示。
前0~15次都会调用invoke0,这是一个native的函数,代码如下所示。
private static native Object invoke0(Method m, Object obj, Object[] args);
15次调用以后会使用新的逻辑,利用GeneratedMethodAccessor1来调用反射的方法。
MethodAccessorGenerator的作用是通过ASM生成新的类sun.reflect.GeneratedMethod-Accessor1。
为了查看生成的类的内容,可以使用阿里的arthas工具。修改上面的代码,在main函数的最后加上“System.in.read();”让JVM进程不要退出。执行arthas工具中的./as.sh,会要求输入JVM进程
选择在运行的进程号为7的ReflectionTest就进入了arthas交互性界面。执行dump sun.reflect.GeneratedMethodAccessor1,将类文件保存到本地,如图3-20所示。
[arthas@54498]$ dump sun.reflect.GeneratedMethodAccessor1
HASHCODE CLASSLOADER LOCATION
7e6cbb7a +-sun.reflect.DelegatingClassLoader@7e6cbb7a /Users/duandian/log
+-sun.misc.Launcher$AppClassLoader@18b4aac2 s/arthas/classdump/
+-sun.misc.Launcher$ExtClassLoader@788f3202 sun.reflect.Delegat
ingClassLoader-7e6c
bb7a/sun/reflect/Ge
neratedMethodAccess
or1.class
/Users/duandian/logs/arthas/classdump/sun.reflect.DelegatingClassLoader-7e6cbb7a/sun/reflect/GeneratedMethodAccessor1.class
package sun.reflect;
import com.zc.srjvm.c06.ReflectionTest;
import java.lang.reflect.InvocationTargetException;
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public Object invoke(Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException {
try {
if (paramArrayOfObject != null && paramArrayOfObject.length != 0)
throw new IllegalArgumentException();
try {
ReflectionTest.foo();
return null;
} catch (Throwable throwable) {
throw new InvocationTargetException(null);
}
} catch (ClassCastException|NullPointerException classCastException) {
throw new IllegalArgumentException(null.toString());
}
}
}
为什么要设置为0~15次使用native方式来调用,15次以后使用ASM新生成的类来处理反射的调用呢?
这是基于性能的考虑,JNI native调用的方式要比动态生成类调用的方式慢20倍左右,但是由于第一次字节码生成的过程比较慢,如果反射仅调用一次的话,采用生成字节码的方式反而比native调用的方式慢3~4倍。为了权衡这两种方式的利弊,Java引入了inflation机制,接下来我们来看看inflation的细节。
反射的inflation机制
很多情况下,反射只会调用一两次,JVM于是想了一个办法,设置了一个sun.reflect.inflationThreshold阈值,默认等于15。当反射方法调用超过15次时(从0开始计算),会使用ASM生成新类,保证后面的调用比native要快。调用次数小于15次的情况下,直接用native的方式来调用,没有额外类的生成、校验、加载的开销。这种方式被称为inflation机制。
JVM与inflation相关的属性有两个,一个是刚提到的阈值sun.reflect.inflationThreshold,还有一个是是否禁用inflation的属性sun.reflect.noInflation,默认值为false。如果把sun.reflect.noInflation这个值设置成true,那么从第0次开始就使用动态生成类的方式来调用反射方法了,而不会使用native的方式。增加noInflation选项重新执行上述Java代码,如下所示。
java -cp . -Dsun.reflect.noInflation=true ReflectionTest
可以看到,从第0次开始就已经没有使用native方法来调用反射方法了。