全面详细解析invoke方法的执行和使用

网友投稿 1159 2022-05-30

invoke方法

在Java中很多方法都会调用invoke方法,很多异常的抛出多会定位到invoke方法:

java.lang.NullPointerException at ...... 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:497)

invoke执行过程

invoke方法用来在运行时动态地调用某个实例的方法,实现如下:

@CallSensitive public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class caller = Refelection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); }

AccessibleObject类是Field,Method和Constructor对象的基类:

提供将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力

invoke方法会首先检查AccessibleObject的override属性的值:

override默认值为false:

表示需要权限调用规则,调用方法时需要检查权限

也可以使用setAccessible() 设置为true

override如果值为true:

表示忽略权限规则,调用方法时无需检查权限

全面详细解析invoke方法的执行和使用

也就是说,可以调用任意private方法,违反了封装

如果override属性为默认值false,则进行进一步的权限检查:

首先用Reflection.quickCheckMemberAccess(clazz, modifiers) 方法检查方法是否为public

1.1 如果是public方法的话,就跳过本步

1.2 如果不是public方法的话,就用Reflection.getCallerClass()方法获取调用这个方法的Class对象,这是一个native方法

@CallerSensitive public static native Class getCallerClass(); ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 在OpenJDK中可以找到getCallerClass方法的JNI入口-Reflection.c JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__ (JNIEnv *env, jclass unused) { return JVM_GetCallerClass(env, JVM_CALLER_DEPTH); } --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - JVM_GetCallerClass的源码位于jvm.cpp中 VM_ENTRY(jclass, JVM_GetCallerClass(JNIEnv* env, int depth)) JVMWrapper("JVM_GetCallerClass"); // Pre-JDK 8 and early builds of JDK 8 don't have a CallerSensitive annotation; or // sun.reflect.Reflection.getCallerClass with a depth parameter is provided // temporarily for existing code to use until a replacement API is defined. if (SystemDictionary::reflect_CallerSensitive_klass() == NULL || depth != JVM_CALLER_DEPTH) { Klass* k = thread->security_get_caller_class(depth); return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror()); } // Getting the class of the caller frame. // // The call stack at this point looks something like this: // // [0] [ @CallerSensitive public sun.reflect.Reflection.getCallerClass ] // [1] [ @CallerSensitive API.method ] // [.] [ (skipped intermediate frames) ] // [n] [ caller ] vframeStream vfst(thread); // Cf. LibraryCallKit::inline_native_Reflection_getCallerClass for (int n = 0; !vfst.at_end(); vfst.security_next(), n++) { Method* m = vfst.method(); assert(m != NULL, "sanity"); switch (n) { case 0: // This must only be called from Reflection.getCallerClass if (m->intrinsic_id() != vmIntrinsics::_getCallerClass) { THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), "JVM_GetCallerClass must only be called from Reflection.getCallerClass"); } // fall-through case 1: // Frame 0 and 1 must be caller sensitive. if (!m->caller_sensitive()) { THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), err_msg("CallerSensitive annotation expected at frame %d", n)); } break; default: if (!m->is_ignored_by_security_stack_walk()) { // We have reached the desired frame; return the holder class. return (jclass) JNIHandles::make_local(env, m->method_holder()->java_mirror()); } break; } } return NULL; JVM_END

获取Class对象caller后使用checkAccess方法进行一次快速的权限校验 ,checkAccess方法实现如下:

volatile Object securityCheckCache; void checkAccess(Class caller, Class clazz, Object obj, int modifiers) throws IllegalAccessException { if(caller == clazz){ // 快速校验 return; // 权限通过校验 } Object cache = securityCheckCache; // 读取volatile Class targetClass = clazz; if (obj != null && Modifier.isProtected(modifiers) && ((targetClass = obj.getClass()) != clazz)) { // 必须匹配caller,targetClass中的一个 if (cache instanceof Class[]) { Class[] cache2 = (Class[]) cache; if (cache2[1] == targetClass && cache[0] == caller) { return; // 校验通过 } } } else if (cache == caller) { return; // 校验通过 } slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass); }

首先先执行一次快速校验,一旦Class正确则权限检查通过;如果未通过,则创建一个缓存,中间再进行检查

如果上面所有的权限检查都未通过,将会执行更详细的检查:

void slowCheckMemberAccess(Class caller, Class clazz, Object obj, int modifiers, Class targetClass) throws IllegalAccessException { Refelection.ensureMemberAccess(caller, clazz, obj, modifiers); // 如果成功,就更新缓存 Object cache = ((targetClass == clazz) ? caller : new Class[] {caller, targetClass}); securityCheckCache = cache; }

用Reflection.ensureMemberAccess方法继续检查权限.若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制

由于JMM的happens-before规则能够保证缓存初始化能够在写缓存之间发生,因此两个cache不需要声明为volatile

检查权限的工作到此结束.如果没有通过检查就会抛出异常,如果通过检查就会到下一步

Method.invoke() 不是自身实现反射调用逻辑,而是通过sun.refelect.MethodAccessor来处理

Method对象的基本构成:

每个Java方法有且只有一个Method对象作为root, 相当于根对象,对用户不可见

当创建Method对象时,代码中获得的Method对象相当于其副本或者引用

root对象持有一个MethodAccessor对象,所有获取到的Method对象都共享这一个MethodAccessor对象

必须保证MethodAccessor在内存中的可见性

root对象及其声明:

private volatile MethodAccessor methodAccessor; /** * For sharing of MethodAccessors. This branching structure is * currently only two levels deep (i.e., one root Method and * potentially many Method objects pointing to it.) * * If this branching structure would ever contain cycles, deadlocks can * occur in annotation code. */ private Method root;

MethodAccessor:

/** * This interface provides the declaration for * java.lang.reflect.Method.invoke(). Each Method object is * configured with a (possibly dynamically-generated) class which * implements this interface */ public interface MethodAccessor { // Matches specification in {@link java.lang.reflect.Method} public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException; }

MethodAccessor是一个接口,定义了invoke() 方法,通过Usage可以看出MethodAccessor的具体实现类:

sun.reflect.DelegatingMethodAccessorImpl

sun.reflect.MethodAccessorImpl

sun.reflect.NativeMethodAccessorImpl

第一次调用Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccess对象还没有创建

第一次调用时,才开始创建MethodAccessor并更新为root, 然后调用MethodAccessor.invoke() 完成反射调用

/** * NOTE that there is no synchronization used here. * It is correct(though not efficient) to generate more than one MethodAccessor for a given Method. * However, avoiding synchronization will probably make the implementation more scalable. */ 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 { tmp = reflectionFactory.newMethodAccessor(this); setMethodAccessor(tmp); } return tmp; }

methodAccessor实例由reflectionFactory对象操控生成 ,reflectionFactory是在AccessibleObject中声明的:

/** * Reflection factory used by subclasses for creating field, * method, and constructor accessors. Note that this is called very early in the bootstrapping process. */ static final ReflectionFactory reflectionFactory = AccessController.doPrivileged( new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());

sun.reflect.ReflectionFactory方法:

public class ReflectionFactory { private static boolean initted = false; private static Permission reflectionFactoryAccessPerm = new RuntimePermission("reflectionFactoryAccess"); private static ReflectionFactory soleInstance = new ReflectionFactory(); // Provides access to package-private mechanisms in java.lang.reflect private static volatile LangReflectAccess langReflectAccess; /** * "Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor. * newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster) * Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves * To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations */ // Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl private static noInflation = false; private static int inflationThreshold = 15; // 生成MethodAccessor public MethodAccessor newMethodAccessor(Method method) { checkInitted(); if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) { 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; } } /** * We have to defer full initialization of this class until after the static initializer is run since java.lang.reflect * Method's static initializer (more properly, that for java.lang.reflect.AccessibleObject) causes this class's to be run, before the system properties are set up */ private static void checkInitted() { if (initted) return; AccessController.doPrivileged( new PrivilegedAction() { public Void run() { /** * Tests to ensure the system properties table is fully initialized * This is needed because reflection code is called very early in the initialization process (before command-line arguments have been parsed and therefore these user-settable properties installed * We assume that if System.out is non-null then the System class has been fully initialized and that the bulk of the startup code has been 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 new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e); } } initted = true; return null; } }); } }

实际的MethodAccessor实现有两个版本,一个是Java版本,一个是native版本,两者各有特点:

初次启动时Method.invoke() 和Constructor.newInstance() 方法采用native方法要比Java方法快3-4倍

启动后native方法又要消耗额外的性能而慢于Java方法

Java实现的版本在初始化时需要较多时间,但长久来说性能较好

这是HotSpot的优化方式带来的性能特性:

跨越native边界会对优化有阻碍作用

为了尽可能地减少性能损耗,HotSpot JDK采用inflation方式:

Java方法在被反射调用时,开头若干次使用native版

等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke() 方法的字节码

以后对该Java方法的反射调用就会使用Java版本

ReflectionFactory.newMethodAccessor() 生成MethodAccessor对象的逻辑:

native版开始会会生成NativeMethodAccessorImpl和DelegatingMethodAccessorImpl两个对象

DelegatingMethodAccessorImpl:

/* Delegates its invocation to another MethodAccessorImpl and can change its delegate at run time */ class DelegatingMethodAccessorImpl extends MethodAccessorImpl { private MethodAccessorImpl delegate; DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) { setDelegate(delegate); } public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException { return delegate.invoke(obj, args); } void setDelegate(MethodAccessorImpl delegate) { this.delegate = delegate; } }

DelegatingMethodAccessorImpl对象是一个中间层,方便在native版与Java版的MethodAccessor之间进行切换

native版MethodAccessor的Java方面的声明: sun.reflect.NativeMethodAccessorImpl

/* Used only for the first few invocations of a Method; afterward,switches to bytecode-based implementation */ class NativeMethodAccessorImpl extends MethodAccessorImpl { private Method method; private DelegatingMethodAccessorImpl parent; private int numInvocations; NativeMethodAccessorImpl(Method method) { this.method = method; } public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException { /* We can't inflate methods belonging to vm-anonymous classes because that kind of class can't be referred to by name, hence can't be found from the generated bytecode */ if (++numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) { 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); } void setParent(DelegatingMethodAccessorImpl parent) { this.parent = parent; } private static native Object invoke0(Method m, Object obj, Object[] args); }

每次NativeMethodAccessorImpl.invoke() 方法被调用时,程序调用计数器都会增加1, 看看是否超过阈值

如果超过则调用MethodAccessorGenerator.generateMethod() 来生成Java版的MethodAccessor的实现类

改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版

经由DelegatingMethodAccessorImpl.invoke() 调用到的就是Java版的实现

JVM层invoke0方法

invoke0方法是一个native方法,在HotSpot JVM里调用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); }

openjdk/hotspot/src/share/vm/prims/jvm.cpp:

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: openjdk/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); }

Java的对象模型 :klass和oop

Java版的实现

Java版MethodAccessor的生成使用MethodAccessorGenerator实现

Generator for sun.reflect.MethodAccessor and sun.reflect.ConstructorAccessor objects using bytecodes to implement reflection. A java.lang.reflect.Method or java.lang.reflect.Constructor object can delegate its invoke or newInstance method to an accessor using native code or to one generated by this class. (Methods and Constructors were merged together in this class to ensure maximum code sharing.)

运用了asm动态生成字节码技术 - sun.reflect.ClassFileAssembler

invoke总结

invoke方法的过程:

MagicAccessorImpl:

原本Java的安全机制使得不同类之间不是任意信息都可见,但JDK里面专门设了个MagicAccessorImpl标记类开了个后门来允许不同类之间信息可以互相访问,由JVM管理

/**

MagicAccessorImpl (named for parity with FieldAccessorImpl and others, not because it actually implements an interface) is a marker class in the hierarchy. All subclasses of this class are "magically" granted access by the VM to otherwise inaccessible fields and methods of other classes. It is used to hold the code for dynamically-generated FieldAccessorImpl and MethodAccessorImpl subclasses. (Use of the word "unsafe" was avoided in this class's name to avoid confusion with {@link sun.misc.Unsafe}.)

The bug fix for 4486457 also necessitated disabling verification for this class and all subclasses, as opposed to just SerializationConstructorAccessorImpl and subclasses, to avoid having to indicate to the VM which of these dynamically-generated stub classes were known to be able to pass the verifier.

Do not change the name of this class without also changing the VM's code.

*/ class MagicAccessorImpl { }

@CallerSensitive注解

Summary: Improve the security of the JDK’s method-handle implementation by replacing the existing hand-maintained list of caller-sensitive methods with a mechanism that accurately identifies such methods and allows their callers to be discovered reliably.

/** * A method annotated @CallerSensitive is sensitive to its calling class, * via {@link sun.reflect.Reflection#getCallerClass Reflection.getCallerClass}, * or via some equivalent. * * @author John R. Rose */ @Retention(RetentionPolicy.RUNTIME) @Target({METHOD}) public @interface CallerSensitive { }

用 @CallerSensitive注解修饰的方法从一开始就知道具体调用此方法的对象

不用再经过一系列的检查就能确定具体调用此方法的对象

实际上是调用sun.reflect.Reflection.getCallerClass方法

Reflection类位于调用栈中的0帧位置

sun.reflect.Reflection.getCallerClass() 方法返回调用栈中从0帧开始的第x帧中的类实例

该方法提供的机制可用于确定调用者类,从而实现"感知调用者(Caller Sensitive)"的行为

即允许应用程序根据调用类或调用栈中的其它类来改变其自身的行为

反射注意点

反射会额外消耗系统资源,如果不需要动态地创建一个对象,就不要使用反射

反射调用方法时可以忽略权限检查.可能会破坏封装性而导致安全问题

Java JVM

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【华为云Stack】【大架光临】第4期:IaaS首席架构师的架构设计思考与实践
下一篇:领域驱动设计(DDD)研究
相关文章