1. 程式人生 > >深入解析Java反射-invoke方法

深入解析Java反射-invoke方法

上篇文章中回顧了一下Java反射相關的基礎內容。這一節我們來深入研究Method類中的invoke方法,探尋它的奧祕。
注:本篇文章的所有原始碼都基於OpenJDK 1.8。

引入

即使沒有學過反射,大家也一定會見過invoke方法。因為很多方法呼叫都是靠invoke方法,所以很多異常的丟擲都會定位到invoke方法,比如下面的情形大家會很熟悉:

123456
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)

大家在看到異常丟擲時,除了想要排除Bug,是不是同時也對這個神祕的invoke乃至invoke0方法有一些好奇呢?我們下面就來揭開它神祕的面紗,探尋底層的機制。

淺析invoke過程

上一篇文章我們講過,invoke方法用來在執行時動態地呼叫某個例項的方法。它的實現如下:

1234567891011121314151617
@CallerSensitivepublic 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(); } return ma.invoke(obj, args);}

我們根據invoke方法的實現,將其分為以下幾步:

1、許可權檢查

invoke方法會首先檢查AccessibleObject的override屬性的值。AccessibleObject 類是 Field、Method 和 Constructor 物件的基類。它提供了將反射的物件標記為在使用時取消預設 Java 語言訪問控制檢查的能力。
override的值預設是false,表示需要許可權呼叫規則,呼叫方法時需要檢查許可權;我們也可以用setAccessible方法設定為true,若override的值為true,表示忽略許可權規則,呼叫方法時無需檢查許可權(也就是說可以呼叫任意的private方法,違反了封裝)。
如果override屬性為預設值false,則進行進一步的許可權檢查:
(1)首先用Reflection.quickCheckMemberAccess(clazz, modifiers)方法檢查方法是否為public,如果是的話跳出本步;如果不是public方法,那麼用Reflection.getCallerClass()方法獲取呼叫這個方法的Class物件,這是一個native方法:

12
@CallerSensitive    public static native Class<?> getCallerClass();

在OpenJDK的原始碼中找到此方法的JNI入口(Reflection.c):

12345
JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__(JNIEnv *env, jclass unused){    return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);}

其中JVM_GetCallerClass的原始碼如下,有興趣的可以研究一下(位於jvm.cpp):

12345678910111213141516171819202144454647
JVM_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方法做一次快速的許可權校驗,其實現為:

123262728293031
volatile Object securityCheckCache;    void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)        throws IllegalAccessException    {        if (caller == clazz) {  // 快速校驗            return;             // 許可權通過校驗        }        Object cache = securityCheckCache;  // read volatile        Class<?> targetClass = clazz;        if (obj != null            && Modifier.isProtected(modifiers)            && ((targetClass = obj.getClass()) != clazz)) {            // Must match a 2-list of { caller, targetClass }.            if (cache instanceof Class[]) {                Class<?>[] cache2 = (Class<?>[]) cache;                if (cache2[1] == targetClass &&                    cache2[0] == caller) {                    return;     // ACCESS IS OK                }                // (Test cache[1] first since range check for [1]                // subsumes range check for [0].)            }        } else if (cache == caller) {            // Non-protected case (or obj.class == this.clazz).            return;             // ACCESS IS OK        }        // If no return, fall through to the slow path.        slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);    }

首先先執行一次快速校驗,一旦呼叫方法的Class正確則許可權檢查通過。
若未通過,則建立一個快取,中間再進行一堆檢查(比如檢驗是否為protected屬性)。
如果上面的所有許可權檢查都未通過,那麼將執行更詳細的檢查,其實現為:

123456789101112131415161718
// Keep all this slow stuff out of line:void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,                           Class<?> targetClass)    throws IllegalAccessException{    Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);    // Success: Update the cache.    Object cache = ((targetClass == clazz)                    ? caller                    : new Class<?>[] { caller, targetClass });    // Note:  The two cache elements are not volatile,    // but they are effectively final.  The Java memory model    // guarantees that the initializing stores for the cache    // elements will occur before the volatile write.    securityCheckCache = cache;         // write volatile}

大體意思就是,用Reflection.ensureMemberAccess方法繼續檢查許可權,若檢查通過就更新快取,這樣下一次同一個類呼叫同一個方法時就不用執行許可權檢查了,這是一種簡單的快取機制。由於JMM的happens-before規則能夠保證快取初始化能夠在寫快取之前發生,因此兩個cache不需要宣告為volatile。
到這裡,前期的許可權檢查工作就結束了。如果沒有通過檢查則會丟擲異常,如果通過了檢查則會到下一步。

2、呼叫MethodAccessor的invoke方法

Method.invoke()實際上並不是自己實現的反射呼叫邏輯,而是委託給sun.reflect.MethodAccessor來處理。
首先要了解Method物件的基本構成,每個Java方法有且只有一個Method物件作為root,它相當於根物件,對使用者不可見。當我們建立Method物件時,我們程式碼中獲得的Method物件都相當於它的副本(或引用)。root物件持有一個MethodAccessor物件,所以所有獲取到的Method物件都共享這一個MethodAccessor物件,因此必須保證它在記憶體中的可見性。root物件其宣告及註釋為:

12345678
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到底是個啥玩意呢?

12345678910
/** 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可得它的具體實現類有:

  • sun.reflect.DelegatingMethodAccessorImpl
  • sun.reflect.MethodAccessorImpl
  • sun.reflect.NativeMethodAccessorImpl

第一次呼叫一個Java方法對應的Method物件的invoke()方法之前,實現呼叫邏輯的MethodAccessor物件還沒有建立;等第一次呼叫時才新建立MethodAccessor並更新給root,然後呼叫MethodAccessor.invoke()完成反射呼叫:

12345678910111213141516171819
// 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 {        // Otherwise fabricate one and propagate it up to the root        tmp = reflectionFactory.newMethodAccessor(this);        setMethodAccessor(tmp);    }    return tmp;}

可以看到methodAccessor例項由reflectionFactory物件操控生成,它在AccessibleObject下的宣告如下:

123456
// 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類的原始碼:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555675767778919293949596
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 boolean 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<Void>() {                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實現的版本在初始化時需要較多時間,但長久來說效能較好;native版本正好相反,啟動時相對較快,但執行時間長了之後速度就比不過Java版了。這是HotSpot的優化方式帶來的效能特性,同時也是許多虛擬機器的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機器難以分析也將其內聯,於是執行時間長了之後反而是託管版本的程式碼更快些。

為了儘可能地減少效能損耗,HotSpot JDK採用“inflation”的技巧:讓Java方法在被反射呼叫時,開頭若干次使用native版,等反射呼叫次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的位元組碼,以後對該Java方法的反射呼叫就會使用Java版本。 這項優化是從JDK 1.4開始的。

研究ReflectionFactory.newMethodAccessor()生產MethodAccessor物件的邏輯,一開始(native版)會生產NativeMethodAccessorImpl和DelegatingMethodAccessorImpl兩個物件。
DelegatingMethodAccessorImpl的原始碼如下:

1234567891011121314151617181920
/** 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;    }}

它其實是一箇中間層,方便在native版與Java版的MethodAccessor之間進行切換。
然後下面就是native版MethodAccessor的Java方面的宣告:
sun.reflect.NativeMethodAccessorImpl:

1225262728293031323334353637383940
/** 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版的實現了。
到這裡,我們已經追尋到native版的invoke方法在Java一側宣告的最底層 - invoke0了,下面我們將深入到HotSpot JVM中去研究其具體實現。

尋根溯源 - 在JVM層面探究invoke0方法

invoke0方法是一個native方法,它在HotSpot JVM裡呼叫JVM_InvokeMethod函式:

12345
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

1234567891011121314151617181920212223
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

1234567891011121314151617181920212223
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實現,由於程式碼太長,這裡就不貼程式碼了,只貼一下開頭的註釋:

1234567
/** 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),原理比較複雜,後面講到AOP要用到asm技術的時候再深入瞭解一下吧。

本篇總結

簡單地畫了個圖表示invoke方法的過程,日後再更時序圖:

invoke方法的過程

番外篇

  1. MagicAccessorImpl是什麼鬼?

原本Java的安全機制使得不同類之間不是任意資訊都可見,但JDK裡面專門設了個MagicAccessorImpl標記類開了個後門來允許不同類之間資訊可以互相訪問(由JVM管理):

1234567891011121314151617181920
/** <P> 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}.) </P>    <P> 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. </P>    <P> Do not change the name of this class without also changing the    VM's code. </P> */class MagicAccessorImpl {}
  1. @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.

JDK 1.8才引進了這個註解,因此在老版本的反射實現裡並沒有這個玩意。這是它的定義:

1234567891011
/** * 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)”的行為,即允許應用程式根據呼叫類或呼叫棧中的其它類來改變其自身的行為。

Reference