在框架中经常会会用到method.invoke()方法,用来执行某个的对象的目标方法。以前写代码用到反射时,总是获取先获取method,然后传入对应的class实例对象执行方法。然而前段时间研究invoke方法时,发现invoke方法居然包含多态的特性,这是以前没有考虑过的一个问题。那么method.invoke()方法的执行过程是怎么实现的?它的多态又是如何实现的呢?
本文将从java和jvm的源码实现深入探讨invoke方法的实现过程。
首先给出invoke方法多态特性的演示代码:
public class methodinvoke {
public static void main(string[] args) throws exception {
method animalmethod = animal.class.getdeclaredmethod("print");
method catmethod = cat.class.getdeclaredmethod("print");
animal animal = new animal();
cat cat = new cat();
animalmethod.invoke(cat);
animalmethod.invoke(animal);
catmethod.invoke(cat);
catmethod.invoke(animal);
}
}
class animal {
public void print() {
system.out.println("animal.print()");
}
}
class cat extends animal {
@override
public void print() {
system.out.println("cat.print()");
}
}
代码中,cat类覆盖了父类animal的print()方法, 然后通过反射分别获取print()的method对象。最后分别用cat和animal的实例对象去执行print()方法。其中animalmethod.invoke(animal)和catmethod.invoke(cat),示例对象的真实类型和method的声明classs是相同的,按照预期打印结果;animalmethod.invoke(cat)中,由于cat是animal的子类,按照多态的特性,子类调用父类的的方法,方法执行时会动态链接到子类的实现方法上。因此,这里会调用cat.print()方法;而catmethod.invoke(animal)中,传入的参数类型animal是父类,却期望调用子类cat的方法,因此这一次会抛出异常。代码打印结果为:
cat.print()
animal.print()
cat.print()
exception in thread "main" java.lang.illegalargumentexception: object is not an instance of declaring class
at sun.reflect.nativemethodaccessorimpl.invoke0(native method)
at sun.reflect.nativemethodaccessorimpl.invoke(unknown source)
at sun.reflect.delegatingmethodaccessorimpl.invoke(unknown source)
at java.lang.reflect.method.invoke(unknown source)
at com.wy.invoke.methodinvoke.main(methodinvoke.java:17)
接下来,我们来看看invoke()方法的实现过程。
public object invoke(object obj, object... args) throws illegalaccessexception, illegalargumentexception, invocationtargetexception
{
if (!override) {
if (!reflection.quickcheckmemberaccess(clazz, modifiers)) {
class caller = reflection.getcallerclass(1);
checkaccess(caller, clazz, obj, modifiers);
}
}
methodaccessor ma = methodaccessor; // read volatile
if (ma == null) {
ma = acquiremethodaccessor();
}
return ma.invoke(obj, args);
}
invoke()方法中主要分为两部分:访问控制检查和调用methodaccessor.invoke()实现方法执行。
首先看一下访问控制检查这一块的逻辑。第一眼看到这里的逻辑的时候,很容易搞不清楚是干嘛的。通俗来讲就是通过方法的修饰符(public/protected/private/package),来判断方法的调用者是否可以访问该方法。这是java的基础内容,不过用代码写出来,一下子不容易想到。访问控制检查分为3步:
- 检查override,如果override为true,跳过检查;否则继续;
- 快速检查,判断该方法的修饰符modifiers是否为public,如果是跳过检查;否则继续;
- 详细检查,通过方法的(protected/private/package)修饰符或方法的声明类(例如子类可以访问父类的protected方法)与调用者caller之间的关系,判断caller是否有权限访问该方法。
override属性是method的父类accessibleobject中声明的变量,使得程序可以控制是否跳过访问权限的检查。另外,method的实例对象中,override属性的初始值设置为false。
public void setaccessible(boolean flag) throws securityexception {
securitymanager sm = system.getsecuritymanager();
if (sm != null) sm.checkpermission(access_permission);
setaccessible0(this, flag);
}
private static void setaccessible0(accessibleobject obj, boolean flag)
throws securityexception
{
if (obj instanceof constructor && flag == true) {
constructor c = (constructor)obj;
if (c.getdeclaringclass() == class.class) {
throw new securityexception("can not make a java.lang.class"
" constructor accessible");
}
}
obj.override = flag;
}
多说一句,field同样继承了accessibleobject,且field的override也是初始化为false的,也就是说并没有按照变量的修饰符去初始化不同的值。但是我们在调用field.set(object obj, object value)时,如果该field是private修饰的,会因没有访问权限而抛出异常,因此必须调用setaccessible(true)。此处非常容易理解为因为变量是public的,所以override就被初始化为true。
invoke()方法中,访问控制检查之后,就是通过methodaccessor.invoke()调用方法。再来看一下代码:
methodaccessor ma = methodaccessor; // read volatile
if (ma == null) {
ma = acquiremethodaccessor();
}
return ma.invoke(obj, args);
这里的逻辑很简单,首先将变量methodaccessor赋值给ma,在方法栈中保存一个可以直接引用的本地变量,如果methodaccessor不存在,调用acquiremethodaccessor()方法创建一个。
private volatile methodaccessor methodaccessor;
private method root;
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;
}
void setmethodaccessor(methodaccessor accessor) {
methodaccessor = accessor;
// propagate up
if (root != null) {
root.setmethodaccessor(accessor);
}
}
method copy() {
method res = new method(clazz, name, parametertypes, returntype,
exceptiontypes, modifiers, slot, signature,
annotations, parameterannotations, annotationdefault);
res.root = this;
res.methodaccessor = methodaccessor;
return res;
}
综合acquiremethodaccessor(),setmethodaccessor()以及copy()这三个方法,可以看到一个method实例对象维护了一个root引用。当调用method.copy()进行方法拷贝时,root指向了被拷贝的对象。那么当一个method被多次拷贝后,调用一次setmethodaccessor()方法,就会将root引用所指向的method的methodaccessor变量同样赋值。例如:d -> c -> b -> a,其中x-> y表示x = y.copy(), 当c对象调用setmethodaccessor()时,b和a都会传播赋值methodaccessor, 而d的methodaccessor还是null。紧接着,当d需要获取methodaccessor而调用acquiremethodaccessor()时,d获取root的methodaccessor, 那么d将和abc持有相同的methodaccessor。
虽然method中,通过维护root引用意图使相同的方法始终保持只有一个methodaccessor实例,但是上述方法仍然无法保证相同的方法只有一个methodaccessor实例。例如通过copy()使abcd保持关系:d -> c -> b -> a, 当b对象调用setmethodaccessor()时,b和a都会赋值methodaccessor, 而c、d的methodaccessor还是null。这时d调用acquiremethodaccessor()时,d获取root也就是c的methodaccessor,发现为空,然后就新创建了一个。从而出现了相同的方法中出现了两个methodaccessor实例对象的现象。
在class.getmethod()、class.getdeclaredmethod()以及class.getdeclaredmethod(string name, class... parametertypes)方法中最终都会调用copy()方法来保障method使用的安全性。 在比较极端加巧合的情况下,可能会引起类膨胀的问题,这就是接下来要讲到的methodaccessor的实现机制。
在前面代码中,methodaccessor的创建是通过反射工厂reflectionfactory的newmethodaccessor(method)方法来创建的。
public methodaccessor newmethodaccessor(method method) {
checkinitted();
if (noinflation) {
return new methodaccessorgenerator().
generatemethod(method.getdeclaringclass(),
method.getname(),
method.getparametertypes(),
method.getreturntype(),
method.getexceptiontypes(),
method.getmodifiers());
} else {
nativemethodaccessorimpl acc =
new nativemethodaccessorimpl(method);
delegatingmethodaccessorimpl res =
new delegatingmethodaccessorimpl(acc);
acc.setparent(res);
return res;
}
}
其中, checkinitted()方法检查从配置项中读取配置并设置noinflation、inflationthreshold的值:
private static void checkinitted() {
if (initted) return;
accesscontroller.doprivileged(
new privilegedaction() {
public void run() {
if (system.out == null) {
// java.lang.system not yet fully initialized
return null;
}
string val = system.getproperty("sun.reflect.noinflation");
if (val != null && val.equals("true")) {
noinflation = true;
}
val = system.getproperty("sun.reflect.inflationthreshold");
if (val != null) {
try {
inflationthreshold = integer.parseint(val);
} catch (numberformatexception e) {
throw (runtimeexception)
new runtimeexception("unable to parse property sun.reflect.inflationthreshold").
initcause(e);
}
}
initted = true;
return null;
}
});
}
可以通过启动参数-dsun.reflect.noinflation=false -dsun.reflect.inflationthreshold=15来设置:
结合字面意思及下面代码理解,这两个配置sun.reflect.noinflation是控制是否立即进行类膨胀,sun.reflect.inflationthreshold是设置类膨胀阈值。
创建methodaccessor有两种选择,一种是当sun.reflect.noinflation配置项为true时,reflectionfactory利用methodaccessor的字节码生成类 methodaccessorgenerator直接创建一个代理类,通过间接调用原方法完成invoke()任务,具体实现稍后给出。methodaccessor的另一种实现方式是,创建delegatingmethodaccessorimpl 委托类,并将执行invoke()方法的具体内容交由nativemethodaccessorimpl实现,而nativemethodaccessorimpl最终调用native方法完成invoke()任务。以下是nativemethodaccessorimpl的invoke()方法实现。
public object invoke(object obj, object[] args)
throws illegalargumentexception, invocationtargetexception
{
if ( numinvocations > reflectionfactory.inflationthreshold()) {
methodaccessorimpl acc = (methodaccessorimpl)
new methodaccessorgenerator().
generatemethod(method.getdeclaringclass(),
method.getname(),
method.getparametertypes(),
method.getreturntype(),
method.getexceptiontypes(),
method.getmodifiers());
parent.setdelegate(acc);
}
return invoke0(method, obj, args);
}
private static native object invoke0(method m, object obj, object[] args);
可以看到,当numinvocations数量大于配置项sun.reflect.inflationthreshold即类膨胀阈值时, 使用methodaccessorgenerator创建一个代理类对象,并且将被委托的nativemethodaccessorimpl的parent,也就是委托类delegatingmethodaccessorimpl的委托类设置为这个生成的代理对象。这么说可能有点绕,下面用一幅图表示这个过程。
总体来说,当调用invoke()时,按照默认配置,method首先创建一个delegatingmethodaccessorimpl对象,并设置一个被委托的nativemethodaccessorimpl对象,那么method.invoke()就被转换成delegatingmethodaccessorimpl.invoke(),然后又被委托给nativemethodaccessorimp.invoke()实现。当nativemethodaccessorimp.invoke()调用次数超过一定热度时(默认15次),被委托方又被转换成代理类来实现。
之前提到过在极端情况下,同一个方法的method对象存在多个不同拷贝拷贝时,可能存在多个methodaccessor对象。那么当多次调用后,必然会生成两个重复功能的代理类。当然,一般情况下,生成两个代理类并没有较大的影响。
其中代理类的具体字节码实现过程较为复杂,大体思想是生成一个如下所示的类:
public class generatedmethodaccessor1 extends methodaccessorimpl {
public generatedmethodaccessor1 () {
super();
}
public object invoke(object obj, object[] args)
throws illegalargumentexception, invocationtargetexception
{
if (!(obj instanceof cat)) {
throw new classcastexception();
}
if (args != null && args.length != 0) {
throw new illegalargumentexception();
}
try {
cat cat = (cat) obj;
cat.print();
return null;
} catch (throwable e) {
throw new invocationtargetexception(e, "invoke error");
}
}
}
到目前为止,除了在代理的generatedmethodaccessor1 类中,方法的执行有多态的特性,而nativemethodaccessorimp的invoke()实现是在jdk中的完成的。接下来我们将目光移到nativemethodaccessorimp的native方法invoke0();
openjdk下载地址
首先,在\jdk\src\share\native\sun\reflect路径下找到nativeaccessors.c, 其中有java_sun_reflect_nativemethodaccessorimpl _invoke0()方法,根据jni定义函数名的规则"包名_类名_方法名",这就是我们要找的native方法实现入口。
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_invokemethod(), 一般以jvm_开头的函数定义在jvm.cpp文件中,不熟悉的话可以通过头文件jvm.h看出来。继续追踪,发现jvm.cpp文件位于spot\src\share\vm\prims文件夹下。
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
其中oop result = reflection::invoke_method(method_handle(), receiver, args, check_null)是方法的执行过程,在\hotspot\src\share\vm\runtime路径下找到reflection.cpp文件。
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_klass(mirror));
method* 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);
}
oop reflection::invoke(instanceklasshandle klass, methodhandle reflected_method,
handle receiver, bool override, objarrayhandle ptypes,
basictype rtype, objarrayhandle args, bool is_method_invoke, traps) {
resourcemark rm(thread);
methodhandle method; // actual method to invoke
klasshandle target_klass; // target klass, receiver's klass for non-static
// ensure klass is initialized
klass->initialize(check_null);
bool is_static = reflected_method->is_static();
if (is_static) {
// ignore receiver argument
method = reflected_method;
target_klass = klass;
} else {
// check for null receiver
if (receiver.is_null()) {
throw_0(vmsymbols::java_lang_nullpointerexception());
}
// check class of receiver against class declaring method
if (!receiver->is_a(klass())) {
throw_msg_0(vmsymbols::java_lang_illegalargumentexception(), "object is not an instance of declaring class");
}
// target klass is receiver's klass
target_klass = klasshandle(thread, receiver->klass());
// no need to resolve if method is private or
if (reflected_method->is_private() || reflected_method->name() == vmsymbols::object_initializer_name()) {
method = reflected_method;
} else {
// resolve based on the receiver
if (reflected_method->method_holder()->is_interface()) {
// resolve interface call
if (reflectionwrapresolutionerrors) {
// new default: 6531596
// match resolution errors with those thrown due to reflection inlining
// linktime resolution & illegalaccesscheck already done by class.getmethod()
method = resolve_interface_call(klass, reflected_method, target_klass, receiver, thread);
if (has_pending_exception) {
// method resolution threw an exception; wrap it in an invocationtargetexception
oop resolution_exception = pending_exception;
clear_pending_exception;
javacallarguments args(handle(thread, resolution_exception));
throw_arg_0(vmsymbols::java_lang_reflect_invocationtargetexception(),
vmsymbols::throwable_void_signature(),
&args);
}
} else {
method = resolve_interface_call(klass, reflected_method, target_klass, receiver, check_(null));
}
} else {
// if the method can be overridden, we resolve using the vtable index.
assert(!reflected_method->has_itable_index(), "");
int index = reflected_method->vtable_index();
method = reflected_method;
if (index != method::nonvirtual_vtable_index) {
// target_klass might be an arrayklassoop but all vtables start at
// the same place. the cast is to avoid virtual call and assertion.
instanceklass* inst = (instanceklass*)target_klass();
method = methodhandle(thread, inst->method_at_vtable(index));
}
if (!method.is_null()) {
// check for abstract methods as well
if (method->is_abstract()) {
// new default: 6531596
if (reflectionwrapresolutionerrors) {
resourcemark rm(thread);
handle h_origexception = exceptions::new_exception(thread,
vmsymbols::java_lang_abstractmethoderror(),
method::name_and_sig_as_c_string(target_klass(),
method->name(),
method->signature()));
javacallarguments args(h_origexception);
throw_arg_0(vmsymbols::java_lang_reflect_invocationtargetexception(),
vmsymbols::throwable_void_signature(),
&args);
} else {
resourcemark rm(thread);
throw_msg_0(vmsymbols::java_lang_abstractmethoderror(),
method::name_and_sig_as_c_string(target_klass(),
method->name(),
method->signature()));
}
}
}
}
}
}
// i believe this is a shouldnotgethere case which requires
// an internal vtable bug. if you ever get this please let karen know.
if (method.is_null()) {
resourcemark rm(thread);
throw_msg_0(vmsymbols::java_lang_nosuchmethoderror(),
method::name_and_sig_as_c_string(klass(),
reflected_method->name(),
reflected_method->signature()));
}
// in the jdk 1.4 reflection implementation, the security check is
// done at the java level
if (!(jdk_version::is_gte_jdk14x_version() && usenewreflection)) {
// access checking (unless overridden by method)
if (!override) {
if (!(klass->is_public() && reflected_method->is_public())) {
bool access = reflection::reflect_check_access(klass(), reflected_method->access_flags(), target_klass(), is_method_invoke, check_null);
if (!access) {
return null; // exception
}
}
}
} // !(universe::is_gte_jdk14x_version() && usenewreflection)
assert(ptypes->is_objarray(), "just checking");
int args_len = args.is_null() ? 0 : args->length();
// check number of arguments
if (ptypes->length() != args_len) {
throw_msg_0(vmsymbols::java_lang_illegalargumentexception(), "wrong number of arguments");
}
// create object to contain parameters for the javacall
javacallarguments java_args(method->size_of_parameters());
if (!is_static) {
java_args.push_oop(receiver);
}
for (int i = 0; i < args_len; i ) {
oop type_mirror = ptypes->obj_at(i);
oop arg = args->obj_at(i);
if (java_lang_class::is_primitive(type_mirror)) {
jvalue value;
basictype ptype = basic_type_mirror_to_basic_type(type_mirror, check_null);
basictype atype = unbox_for_primitive(arg, &value, check_null);
if (ptype != atype) {
widen(&value, atype, ptype, check_null);
}
switch (ptype) {
case t_boolean: java_args.push_int(value.z); break;
case t_char: java_args.push_int(value.c); break;
case t_byte: java_args.push_int(value.b); break;
case t_short: java_args.push_int(value.s); break;
case t_int: java_args.push_int(value.i); break;
case t_long: java_args.push_long(value.j); break;
case t_float: java_args.push_float(value.f); break;
case t_double: java_args.push_double(value.d); break;
default:
throw_msg_0(vmsymbols::java_lang_illegalargumentexception(), "argument type mismatch");
}
} else {
if (arg != null) {
klass* k = java_lang_class::as_klass(type_mirror);
if (!arg->is_a(k)) {
throw_msg_0(vmsymbols::java_lang_illegalargumentexception(), "argument type mismatch");
}
}
handle arg_handle(thread, arg); // create handle for argument
java_args.push_oop(arg_handle); // push handle
}
}
assert(java_args.size_of_parameters() == method->size_of_parameters(), "just checking");
// all oops (including receiver) is passed in as handles. an potential oop is returned as an
// oop (i.e., not as an handle)
javavalue result(rtype);
javacalls::call(&result, method, &java_args, thread);
if (has_pending_exception) {
// method threw an exception; wrap it in an invocationtargetexception
oop target_exception = pending_exception;
clear_pending_exception;
javacallarguments args(handle(thread, target_exception));
throw_arg_0(vmsymbols::java_lang_reflect_invocationtargetexception(),
vmsymbols::throwable_void_signature(),
&args);
} else {
if (rtype == t_boolean || rtype == t_byte || rtype == t_char || rtype == t_short)
narrow((jvalue*) result.get_value_addr(), rtype, check_null);
return box((jvalue*) result.get_value_addr(), rtype, check_null);
}
}
reflection::invoke_method()中调用reflection::invoke(),然后在reflection::invoke()方法中,当反射调用的方法是接口方法时,调用reflection::resolve_interface_call(),该方法依赖linkresolver::resolve_interface_call()来完成方法的动态链接过程,具体实现就不在这里展示。
method = resolve_interface_call(klass, reflected_method, target_klass, receiver, check_(null));
methodhandle reflection::resolve_interface_call(instanceklasshandle klass, methodhandle method,
klasshandle recv_klass, handle receiver, traps) {
assert(!method.is_null() , "method should not be null");
callinfo info;
symbol* signature = method->signature();
symbol* name = method->name();
linkresolver::resolve_interface_call(info, receiver, recv_klass, klass,
name, signature,
klasshandle(), false, true,
check_(methodhandle()));
return info.selected_method();
}
如果反射调用的方法是可以被覆盖的方法,例如animal.print(), reflection::invoke()最终通过查询虚方法表vtable来确定最终的method。
// if the method can be overridden, we resolve using the vtable index.
assert(!reflected_method->has_itable_index(), "");
int index = reflected_method->vtable_index();
method = reflected_method;
if (index != method::nonvirtual_vtable_index) {
// target_klass might be an arrayklassoop but all vtables start at
// the same place. the cast is to avoid virtual call and assertion.
instanceklass* inst = (instanceklass*)target_klass();
method = methodhandle(thread, inst->method_at_vtable(index));
}
1.method.invoke()方法支持多态特性,其native实现在方法真正执行之前通过动态连接或者虚方法表来实现。
2.框架中使用method.invoke()执行方法调用时,初始获取method对象时,可以先调用一次setaccessable(true),使得后面每次调用invoke()时,节省一次方法修饰符的判断,略微提升性能。业务允许的情况下,field同样可以如此操作。
3.委托模式可以解决一种方案的多种实现之间自由切换,而代理模式只能根据传入的被代理对象来实现功能。