简介
上一篇文章[java注解研究]提到注解最终通过生成动态代理实现类方式来调用,而动态代理实际就是通过反射来调用类中的方法,也就是Method.invoke()
调用。Method.invoke()
用途广泛,像刚才所说的动态代理反射调用,框架经常使用的SPI
扩展等。所以本篇有必要了解下Method.invoke()
的实现。
例子
import java.lang.reflect.Method;
public class MethodTest {
public static void main(String[] args) throws Exception {
Class<?> clz = Class.forName("com.method.TestClassLoad");
Object o = clz.newInstance();
Method m = clz.getMethod("test", String.class,Integer.class);
for (int i = 0; i < 16; i++) {
m.invoke(o, "huige",i);
}
}
}
class TestClassLoad {
public void test(String s,Integer i){
System.out.println(s+":测试:"+i);
}
}
注意到MethodTest 类上不会有对类TestClassLoad 的符号依赖——也就是说在加载并初始化MethodTest 类时不需要关心类TestClassLoad 的存在与否,而是等到main()方法执行到调用Class.forName()时才试图对类TestClassLoad 做动态加载;这里用的是一个参数版的forName(),也就是使用当前方法所在类的ClassLoader来加载,并且初始化新加载的类
原理
JDK里Method.invoke()是怎么实现的。
java.lang.reflect.Method#invoke
@CallerSensitive
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();//获取MethodAccessor
}
return ma.invoke(obj, args);
}
private Method root;
//获取MethodAccessor
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;
}
//把新创建methodAccessor对象通过root包装起来
void setMethodAccessor(MethodAccessor accessor) {
methodAccessor = accessor;
// Propagate up
if (root != null) {
root.setMethodAccessor(accessor);
}
}
可以看到Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor
来处理。 每个实际的Java方法只有一个对应的Method对象作为root。这个root是不会暴露给用户的,而是每次在通过反射获取Method对象时,把新创建methodAccessor对象通过root包装起来。在第一次调用一个实际Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()真正完成反射调用。
那么MethodAccessor
是啥呢?
sun.reflect.MethodAccessor
public interface MethodAccessor {
Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}
可以看到它只是一个单方法接口,其invoke()方法与Method.invoke()的对应。
创建MethodAccessor
实例的是ReflectionFactory
。
sun.reflect.ReflectionFactory#newMethodAccessor
private static boolean noInflation = false;
//调用超过15次就采用java版本
private static int inflationThreshold = 15;
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;
}
}
});
}
}
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;
}
从上面代码可以看出:实际的MethodAccessor
实现有两个版本
(1)一个是Java实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;
(2)另一个是native code实现的。native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了权衡两个版本的性能,Sun的JDK使用了inflation
的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值(15次
)时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
验证下:
编译上述代码,并在执行MethodTest 时加入-XX:+TraceClassLoading参数(或者-verbose:class或者直接-verbose都行),如下:
java -XX:+TraceClassLoading MethodTest
如果用IDEA,可以通过
点击Edit Configurations
配置
可以看到输出了一大堆log,把其中相关的部分截取出来如下
huige:测试:0
huige:测试:1
huige:测试:2
huige:测试:3
huige:测试:4
huige:测试:5
huige:测试:6
huige:测试:7
huige:测试:8
huige:测试:9
huige:测试:10
huige:测试:11
huige:测试:12
huige:测试:13
huige:测试:14
[Loaded sun.reflect.ClassFileConstants from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.AccessorGenerator from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorFactory from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVector from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorImpl from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ClassFileAssembler from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.UTF8 from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.Label from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.Label$PatchInfo from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
huige:测试:15
可以看到在执行16次时被触发了,导致JVM新加载了一堆类,其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from JVM_DefineClass]这么一行
具体实现如下:
(1)MethodAccessor
实现版本:开头若干次使用native
版
通过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);
}
//var1就是NativeMethodAccessorImpl var1 = new NativeMethodAccessorImpl(var1);
void setDelegate(MethodAccessorImpl var1) {
this.delegate = var1;
}
当Method.invoke()
调用时,同时调用sun.reflect.DelegatingMethodAccessorImpl#invoke
方法,即调用sun.reflect.NativeMethodAccessorImpl#invoke
方法,代码如下:
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.invoke()方法被调用时,都会增加一个调用次数计数器numInvocations
,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。
注意到关键的invoke0()方法是个native方法。它在HotSpot VM里是由JVM_InvokeMethod()函数所支持的:
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
return JVM_InvokeMethod(env, m, obj, args);
}
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
JVMWrapper("JVM_InvokeMethod");
Handle method_handle;
if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {
method_handle = Handle(THREAD, JNIHandles::resolve(method));
Handle receiver(THREAD, JNIHandles::resolve(obj));
objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));
oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
jobject res = JNIHandles::make_local(env, result);
if (JvmtiExport::should_post_vm_object_alloc()) {
oop ret_type = java_lang_reflect_Method::return_type(method_handle());
assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");
if (java_lang_Class::is_primitive(ret_type)) {
// Only for primitive type vm allocates memory for java object.
// See box() method.
JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);
}
}
return res;
} else {
THROW_0(vmSymbols::java_lang_StackOverflowError());
}
JVM_END
其中的关键又是Reflection::invoke_method()
:
// This would be nicer if, say, java.lang.reflect.Method was a subclass
// of java.lang.reflect.Constructor
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
oop mirror = java_lang_reflect_Method::clazz(method_mirror);
int slot = java_lang_reflect_Method::slot(method_mirror);
bool override = java_lang_reflect_Method::override(method_mirror) != 0;
objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));
oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
BasicType rtype;
if (java_lang_Class::is_primitive(return_type_mirror)) {
rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
} else {
rtype = T_OBJECT;
}
instanceKlassHandle klass(THREAD, java_lang_Class::as_klassOop(mirror));
methodOop m = klass->method_with_idnum(slot);
if (m == NULL) {
THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
}
methodHandle method(THREAD, m);
return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}
(2)MethodAccessor
实现版本:java版本MethodAccessorGenerator
MethodAccessorGenerato如何实现的呢?
具体实现在sun.reflect.MethodAccessorGenerator#generate
方法
代码如下:
private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {
//......
//这里生成[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
final String var13 = generateName(var7, var8);
//。。。。。。
this.targetMethodRef = this.asm.cpi();
if (var7) {
//构建newInstance
this.asm.emitConstantPoolUTF8("newInstance");
} else {
//构建invoke
this.asm.emitConstantPoolUTF8("invoke");
}
//.........
}
最后生成的Java版MethodAccessor大致如下
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public GeneratedMethodAccessor1() {
super();
}
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException {
// prepare the target and parameters
if (obj == null) throw new NullPointerException();
try {
TestClassLoad target = (TestClassLoad) obj;
String arg0 = (String) args[0];
Integer arg1 = (Integer ) args[1];
} catch (ClassCastException e) {
throw new IllegalArgumentException(e.toString());
} catch (NullPointerException e) {
throw new IllegalArgumentException(e.toString());
}
// 调用目标方法
try {
target.test(arg0,arg1 );
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}
总结
就反射调用而言,这个invoke()方法非常干净(然而就“正常调用”而言这额外开销还是明显的)。注意到参数数组被拆开了,把每个参数都恢复到原本没有被Object[]包装前的样子,然后对目标方法做正常的invokevirtual调用。由于在生成代码时已经循环遍历过参数类型的数组,生成出来的代码里就不再包含循环了。
当该反射调用成为热点时,它甚至可以被内联到靠近Method.invoke()的一侧,大大降低了反射调用的开销。而native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。
虽说Sun的JDK这种实现方式使得反射调用方法成本比以前降低了很多,但Method.invoke()本身要用数组包装参数;而且每次调用都必须检查方法的可见性(在Method.invoke()里),也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里);而且Method.invoke()就像是个独木桥一样,各处的反射调用都要挤过去,在调用点上收集到的类型信息就会很乱,影响内联程序的判断,使得Method.invoke()自身难以被内联到调用方。
相比之下MethodHandle
则更有潜力,在其功能完全实现后能达到比普通反射调用方法更高的性能。在使用MethodHandle来做反射调用时,MethodHandle.invoke()的形式参数与返回值类型都是准确的,所以只需要在链接方法的时候才需要检查类型的匹配性,而不必在每次调用时都检查。而且MethodHandle是不可变值,在创建后其内部状态就不会再改变了;JVM可以利用这个知识而放心的对它做激进优化,例如将实际的调用目标内联到做反射调用的一侧。