JAVA深入探究之Method的Invoke方法
前言
在寫(xiě)代碼的時(shí)候,發(fā)現(xiàn)從父類(lèi)class通過(guò)getDeclaredMethod獲取的Method可以調(diào)用子類(lèi)的對(duì)象,而子類(lèi)改寫(xiě)了這個(gè)方法,從子類(lèi)class通過(guò)getDeclaredMethod也能獲取到Method,這時(shí)去調(diào)用父類(lèi)的對(duì)象也會(huì)報(bào)錯(cuò)。雖然這是很符合多態(tài)的現(xiàn)象,也符合java的動(dòng)態(tài)綁定規(guī)范,但還是想弄懂java是如何實(shí)現(xiàn)的,就學(xué)習(xí)了下Method的源代碼。
Method的invoke方法
1.先檢查 AccessibleObject的override屬性是否為true。
AccessibleObject是Method,Field,Constructor的父類(lèi),override屬性默認(rèn)為false,可調(diào)用setAccessible方法改變,如果設(shè)置為true,則表示可以忽略訪(fǎng)問(wèn)權(quán)限的限制,直接調(diào)用。
2.如果不是ture,則要進(jìn)行訪(fǎng)問(wèn)權(quán)限檢測(cè)。用Reflection的quickCheckMemberAccess方法先檢查是不是public的,如果不是再用Reflection.getCallerClass(1)方法獲
得到調(diào)用這個(gè)方法的Class,然后做是否有權(quán)限訪(fǎng)問(wèn)的校驗(yàn),校驗(yàn)之后緩存一次,以便下次如果還是這個(gè)類(lèi)來(lái)調(diào)用就不用去做校驗(yàn)了,直接用上次的結(jié)果,(很奇怪用這種方式緩存,因?yàn)檫@種方式如果下次換個(gè)類(lèi)來(lái)調(diào)用的話(huà),就不用會(huì)緩存了,而再驗(yàn)證一遍,把這次的結(jié)果做為緩存,但上一次的緩存結(jié)果就被沖掉了。這是一個(gè)很簡(jiǎn)單的緩沖機(jī)制,只適用于一個(gè)類(lèi)的重復(fù)調(diào)用)。
3.調(diào)用MethodAccessor的invoke方法。每個(gè)Method對(duì)象包含一個(gè)root對(duì)象,root對(duì)象里持有一個(gè)MethodAccessor對(duì)象。我們獲得的Method獨(dú)享相當(dāng)于一個(gè)root對(duì)象的鏡像,所有這類(lèi)Method共享root里的MethodAccessor對(duì)象,(這個(gè)對(duì)象由ReflectionFactory方法生成,ReflectionFactory對(duì)象在Method類(lèi)中是static final的由native方法實(shí)例化)。
ReflectionFactory生成MethodAccessor:如果noInflation的屬性為true則直接返回MethodAccessorGenerator創(chuàng)建的一個(gè)MethodAccessor。
否則返回DelegatingMethodAccessorImpl,并將他與一個(gè)NativeMethodAccessorImpl互相引用。但DelegatingMethodAccessorImpl執(zhí)行invoke方法的時(shí)候又委托給NativeMethodAccessorImpl了。
再一步深入
4.NativeMethodAccessorImpl的invkoe方法:
調(diào)用natiave方法invoke0執(zhí)行方法調(diào)用.
注意這里有一個(gè)計(jì)數(shù)器numInvocations,每調(diào)用一次方法+1,當(dāng)比 ReflectionFactory.inflationThreshold(15)大的時(shí)候,用MethodAccessorGenerator創(chuàng)建一個(gè)MethodAccessor,并把之前的DelegatingMethodAccessorImpl引用替換為現(xiàn)在新創(chuàng)建的。下一次DelegatingMethodAccessorImpl就不會(huì)再交給NativeMethodAccessorImpl執(zhí)行了,而是交給新生成的java字節(jié)碼的MethodAccessor。
MethodAccessorGenerator使用了asm字節(jié)碼動(dòng)態(tài)加載技術(shù),暫不深入研究。
總結(jié) 一個(gè)方法可以生成多個(gè)Method對(duì)象,但只有一個(gè)root對(duì)象,主要用于持有一個(gè)MethodAccessor對(duì)象,這個(gè)對(duì)象也可以認(rèn)為一個(gè)方法只有一個(gè),相當(dāng)于是static的。因?yàn)镸ethod的invoke是交給MethodAccessor執(zhí)行的,所以我所想要知道的答案在MethodAccessor的invoke中,深入MethodAccessor:
MethodAccessor
public class A { public void foo(String name) { System.out.println("Hello, " + name); } }
可以編寫(xiě)另外一個(gè)類(lèi)來(lái)反射調(diào)用A上的方法:
import java.lang.reflect.Method; public class TestClassLoad { public static void main(String[] args) throws Exception { Class<?> clz = Class.forName("A"); Object o = clz.newInstance(); Method m = clz.getMethod("foo", String.class); for (int i = 0; i < 16; i++) { m.invoke(o, Integer.toString(i)); } } }
注意到TestClassLoad類(lèi)上不會(huì)有對(duì)類(lèi)A的符號(hào)依賴(lài)——也就是說(shuō)在加載并初始化TestClassLoad類(lèi)時(shí)不需要關(guān)心類(lèi)A的存在與否,而是等到main()方法執(zhí)行到調(diào)用Class.forName()時(shí)才試圖對(duì)類(lèi)A做動(dòng)態(tài)加載;這里用的是一個(gè)參數(shù)版的forName(),也就是使用當(dāng)前方法所在類(lèi)的ClassLoader來(lái)加載,并且初始化新加載的類(lèi)?!冒蛇@個(gè)細(xì)節(jié)跟主題沒(méi)啥關(guān)系。
回到主題。這次我的測(cè)試環(huán)境是Sun的JDK 1.6.0 update 13 build 03。編譯上述代碼,并在執(zhí)行TestClassLoad時(shí)加入-XX:+TraceClassLoading參數(shù)(或者-verbose:class或者直接-verbose都行),如下:
控制臺(tái)命令
java -XX:+TraTestClassLoad ceClassLoading
可以看到輸出了一大堆log,把其中相關(guān)的部分截取出來(lái)如下:
[Loaded TestClassLoad from file:/D:/temp_code/test_java_classload/] [Loaded A from file:/D:/temp_code/test_java_classload/] [Loaded sun.reflect.NativeMethodAccessorImpl from shared objects file] [Loaded sun.reflect.DelegatingMethodAccessorImpl from shared objects file] Hello, 0 Hello, 1 Hello, 2 Hello, 3 Hello, 4 Hello, 5 Hello, 6 Hello, 7 Hello, 8 Hello, 9 Hello, 10 Hello, 11 Hello, 12 Hello, 13 Hello, 14 [Loaded sun.reflect.ClassFileConstants from shared objects file] [Loaded sun.reflect.AccessorGenerator from shared objects file] [Loaded sun.reflect.MethodAccessorGenerator from shared objects file] [Loaded sun.reflect.ByteVectorFactory from shared objects file] [Loaded sun.reflect.ByteVector from shared objects file] [Loaded sun.reflect.ByteVectorImpl from shared objects file] [Loaded sun.reflect.ClassFileAssembler from shared objects file] [Loaded sun.reflect.UTF8 from shared objects file] [Loaded java.lang.Void from shared objects file] [Loaded sun.reflect.Label from shared objects file] [Loaded sun.reflect.Label$PatchInfo from shared objects file] [Loaded java.util.AbstractList$Itr from shared objects file] [Loaded sun.reflect.MethodAccessorGenerator$1 from shared objects file] [Loaded sun.reflect.ClassDefiner from shared objects file] [Loaded sun.reflect.ClassDefiner$1 from shared objects file] [Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__] Hello, 15
可以看到前15次反射調(diào)用A.foo()方法并沒(méi)有什么稀奇的地方,但在第16次反射調(diào)用時(shí)似乎有什么東西被觸發(fā)了,導(dǎo)致JVM新加載了一堆類(lèi),其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]這么一行。這是哪里來(lái)的呢?
先來(lái)看看JDK里Method.invoke()是怎么實(shí)現(xiàn)的。
java.lang.reflect.Method:
public final class Method extends AccessibleObject implements GenericDeclaration, Member { // ... 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.) private Method root; // ... public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class caller = Reflection.getCallerClass(1); Class targetClass = ((obj == null || !Modifier.isProtected(modifiers)) ? clazz : obj.getClass()); boolean cached; synchronized (this) { cached = (securityCheckCache == caller) && (securityCheckTargetClassCache == targetClass); } if (!cached) { Reflection.ensureMemberAccess(caller, clazz, obj, modifiers); synchronized (this) { securityCheckCache = caller; securityCheckTargetClassCache = targetClass; } } } } if (methodAccessor == null) acquireMethodAccessor(); return methodAccessor.invoke(obj, args); } // 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 void 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; return; } // Otherwise fabricate one and propagate it up to the root tmp = reflectionFactory.newMethodAccessor(this); setMethodAccessor(tmp); } // ... }
可以看到Method.invoke()實(shí)際上并不是自己實(shí)現(xiàn)的反射調(diào)用邏輯,而是委托給sun.reflect.MethodAccessor來(lái)處理。
每個(gè)實(shí)際的Java方法只有一個(gè)對(duì)應(yīng)的Method對(duì)象作為root,。這個(gè)root是不會(huì)暴露給用戶(hù)的,而是每次在通過(guò)反射獲取Method對(duì)象時(shí)新創(chuàng)建Method對(duì)象把root包裝起來(lái)再給用戶(hù)。在第一次調(diào)用一個(gè)實(shí)際Java方法對(duì)應(yīng)得Method對(duì)象的invoke()方法之前,實(shí)現(xiàn)調(diào)用邏輯的MethodAccessor對(duì)象還沒(méi)創(chuàng)建;等第一次調(diào)用時(shí)才新創(chuàng)建MethodAccessor并更新給root,然后調(diào)用MethodAccessor.invoke()真正完成反射調(diào)用。
那么MethodAccessor是啥呢?
sun.reflect.MethodAccessor:
public interface MethodAccessor { /** Matches specification in {@link java.lang.reflect.Method} */ public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException; }
可以看到它只是一個(gè)單方法接口,其invoke()方法與Method.invoke()的對(duì)應(yīng)。
創(chuàng)建MethodAccessor實(shí)例的是ReflectionFactory。
sun.reflect.ReflectionFactory:
public class ReflectionFactory { private static boolean initted = false; // ... // // "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; // ... /** 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 Object 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 (RuntimeException) new RuntimeException("Unable to parse property sun.reflect.inflationThreshold"). initCause(e); } } initted = true; return null; } }); } // ... 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; } } }
這里就可以看到有趣的地方了。如注釋所述,實(shí)際的MethodAccessor實(shí)現(xiàn)有兩個(gè)版本,一個(gè)是Java實(shí)現(xiàn)的,另一個(gè)是native code實(shí)現(xiàn)的。Java實(shí)現(xiàn)的版本在初始化時(shí)需要較多時(shí)間,但長(zhǎng)久來(lái)說(shuō)性能較好;native版本正好相反,啟動(dòng)時(shí)相對(duì)較快,但運(yùn)行時(shí)間長(zhǎng)了之后速度就比不過(guò)Java版了。這是HotSpot的優(yōu)化方式帶來(lái)的性能特性,同時(shí)也是許多虛擬機(jī)的共同點(diǎn):跨越native邊界會(huì)對(duì)優(yōu)化有阻礙作用,它就像個(gè)黑箱一樣讓虛擬機(jī)難以分析也將其內(nèi)聯(lián),于是運(yùn)行時(shí)間長(zhǎng)了之后反而是托管版本的代碼更快些。
為了權(quán)衡兩個(gè)版本的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調(diào)用時(shí),開(kāi)頭若干次使用native版,等反射調(diào)用次數(shù)超過(guò)閾值時(shí)則生成一個(gè)專(zhuān)用的MethodAccessor實(shí)現(xiàn)類(lèi),生成其中的invoke()方法的字節(jié)碼,以后對(duì)該Java方法的反射調(diào)用就會(huì)使用Java版。
Sun的JDK是從1.4系開(kāi)始采用這種優(yōu)化的。
PS.可以在啟動(dòng)命令里加上-Dsun.reflect.noInflation=true,就會(huì)RefactionFactory的noInflation屬性就變成true了,這樣不用等到15調(diào)用后,程序一開(kāi)始就會(huì)用java版的MethodAccessor了。
上面看到了ReflectionFactory.newMethodAccessor()生產(chǎn)MethodAccessor的邏輯,在“開(kāi)頭若干次”時(shí)用到的DelegatingMethodAccessorImpl代碼如下:
sun.reflect.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; } }
這是一個(gè)間接層,方便在native與Java版的MethodAccessor之間實(shí)現(xiàn)切換。
然后下面就是native版MethodAccessor的Java一側(cè)的聲明:
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 { 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); } void setParent(DelegatingMethodAccessorImpl parent) { this.parent = parent; } private static native Object invoke0(Method m, Object obj, Object[] args); }
每次NativeMethodAccessorImpl.invoke()方法被調(diào)用時(shí),都會(huì)增加一個(gè)調(diào)用次數(shù)計(jì)數(shù)器,看超過(guò)閾值沒(méi)有;一旦超過(guò),則調(diào)用MethodAccessorGenerator.generateMethod()來(lái)生成Java版的MethodAccessor的實(shí)現(xiàn)類(lèi),并且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。后續(xù)經(jīng)由DelegatingMethodAccessorImpl.invoke()調(diào)用到的就是Java版的實(shí)現(xiàn)了。
注意到關(guān)鍵的invoke0()方法是個(gè)native方法。它在HotSpot VM里是由JVM_InvokeMethod()函數(shù)所支持的:
由C編寫(xiě)
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
其中的關(guān)鍵又是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); }
再下去就深入到HotSpot VM的內(nèi)部了,本文就在這里打住吧。有同學(xué)有興趣深究的話(huà)以后可以再寫(xiě)一篇討論native版的實(shí)現(xiàn)。
回到Java的一側(cè)。MethodAccessorGenerator長(zhǎng)啥樣呢?由于代碼太長(zhǎng),這里就不完整貼了,有興趣的可以到OpenJDK 6的Mercurial倉(cāng)庫(kù)看:OpenJDK 6 build 17的MethodAccessorGenerator。它的基本工作就是在內(nèi)存里生成新的專(zhuān)用Java類(lèi),并將其加載。就貼這么一個(gè)方法:
private static synchronized String generateName(boolean isConstructor, boolean forSerialization) { if (isConstructor) { if (forSerialization) { int num = ++serializationConstructorSymnum; return "sun/reflect/GeneratedSerializationConstructorAccessor" + num; } else { int num = ++constructorSymnum; return "sun/reflect/GeneratedConstructorAccessor" + num; } } else { int num = ++methodSymnum; return "sun/reflect/GeneratedMethodAccessor" + num; } }
去閱讀源碼的話(huà),可以看到MethodAccessorGenerator是如何一點(diǎn)點(diǎn)把Java版的MethodAccessor實(shí)現(xiàn)類(lèi)生產(chǎn)出來(lái)的。也可以看到GeneratedMethodAccessor+數(shù)字這種名字是從哪里來(lái)的了,就在上面的generateName()方法里。
對(duì)本文開(kāi)頭的例子的A.foo(),生成的Java版MethodAccessor大致如下:
package sun.reflect; 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 { A target = (A) obj; if (args.length != 1) throw new IllegalArgumentException(); String arg0 = (String) args[0]; } catch (ClassCastException e) { throw new IllegalArgumentException(e.toString()); } catch (NullPointerException e) { throw new IllegalArgumentException(e.toString()); } // make the invocation try { target.foo(arg0); } catch (Throwable t) { throw new InvocationTargetException(t); } } }
就反射調(diào)用而言,這個(gè)invoke()方法非常干凈(然而就“正常調(diào)用”而言這額外開(kāi)銷(xiāo)還是明顯的)。注意到參數(shù)數(shù)組被拆開(kāi)了,把每個(gè)參數(shù)都恢復(fù)到原本沒(méi)有被Object[]包裝前的樣子,然后對(duì)目標(biāo)方法做正常的invokevirtual調(diào)用。由于在生成代碼時(shí)已經(jīng)循環(huán)遍歷過(guò)參數(shù)類(lèi)型的數(shù)組,生成出來(lái)的代碼里就不再包含循環(huán)了。
至此找到我的答案了,因?yàn)镸ethodAccessor會(huì)做強(qiáng)制類(lèi)型轉(zhuǎn)換再進(jìn)行方法調(diào)用,但父類(lèi)強(qiáng)制轉(zhuǎn)化成子類(lèi)的的時(shí)候就會(huì)報(bào)錯(cuò)類(lèi)型不匹配錯(cuò)誤了,所以如果變量的引用聲明是父但實(shí)際指向的對(duì)象是子,那么這種調(diào)用也是可以的。
題外話(huà)
當(dāng)該反射調(diào)用成為熱點(diǎn)時(shí),它甚至可以被內(nèi)聯(lián)到靠近Method.invoke()的一側(cè),大大降低了反射調(diào)用的開(kāi)銷(xiāo)。而native版的反射調(diào)用則無(wú)法被有效內(nèi)聯(lián),因而調(diào)用開(kāi)銷(xiāo)無(wú)法隨程序的運(yùn)行而降低。
雖說(shuō)Sun的JDK這種實(shí)現(xiàn)方式使得反射調(diào)用方法成本比以前降低了很多,但Method.invoke()本身要用數(shù)組包裝參數(shù);而且每次調(diào)用都必須檢查方法的可見(jiàn)性(在Method.invoke()里),也必須檢查每個(gè)實(shí)際參數(shù)與形式參數(shù)的類(lèi)型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里);而且Method.invoke()就像是個(gè)獨(dú)木橋一樣,各處的反射調(diào)用都要擠過(guò)去,在調(diào)用點(diǎn)上收集到的類(lèi)型信息就會(huì)很亂,影響內(nèi)聯(lián)程序的判斷,使得Method.invoke()自身難以被內(nèi)聯(lián)到調(diào)用方。
相比之下JDK7里新的MethodHandler則更有潛力,在其功能完全實(shí)現(xiàn)后能達(dá)到比普通反射調(diào)用方法更高的性能。在使用MethodHandle來(lái)做反射調(diào)用時(shí),MethodHandle.invoke()的形式參數(shù)與返回值類(lèi)型都是準(zhǔn)確的,所以只需要在鏈接方法的時(shí)候才需要檢查類(lèi)型的匹配性,而不必在每次調(diào)用時(shí)都檢查。而且MethodHandle是不可變值,在創(chuàng)建后其內(nèi)部狀態(tài)就不會(huì)再改變了;JVM可以利用這個(gè)知識(shí)而放心的對(duì)它做激進(jìn)優(yōu)化,例如將實(shí)際的調(diào)用目標(biāo)內(nèi)聯(lián)到做反射調(diào)用的一側(cè)。
本來(lái)Java的安全機(jī)制使得不同類(lèi)之間不是任意信息都可見(jiàn),但Sun的JDK里開(kāi)了個(gè)口,有一個(gè)標(biāo)記類(lèi)專(zhuān)門(mén)用于開(kāi)后門(mén):
package sun.reflect; /** <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 { }
那個(gè)"__JVM_DefineClass__"的來(lái)源是這里:
src/share/vm/prims/jvm.cpp
// common code for JVM_DefineClass() and JVM_DefineClassWithSource() // and JVM_DefineClassWithSourceCond() static jclass jvm_define_class_common(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source, jboolean verify, TRAPS) { if (source == NULL) source = "__JVM_DefineClass__";
P.S. log里的"shared objects file",其實(shí)就是rt.jar,為什么要這么顯示,Stack OverFlow上有這樣的回答:
This is Class Data Sharing. When running the Sun/Oracle Client HotSpot and sharing enable (either -Xshare:auto which is the default, or -Xshare:on), the classes.jsa file is memory mapped. This file contains a number of classes (listed in the classlist file) in internal representation suitable for the exact configuration of the machine running it. The idea is that the classes can be loaded quickly, getting the the JVM up faster. Soon enough a class not covered will be hit, and rt.jar will need to be opened and classes loaded conventionally as required.
不能很好理解,大概理解就是所有jvm共享,并可以快速加載里面的class.有英文好的朋友可以留言幫助下。
P.S java內(nèi)聯(lián)函數(shù)
C++是否為內(nèi)聯(lián)函數(shù)由自己決定,Java由編譯器決定。內(nèi)聯(lián)函數(shù)就是指函數(shù)在被調(diào)用的地方直接展開(kāi),編譯器在調(diào)用時(shí)不用像一般函數(shù)那樣,參數(shù)壓棧,返回時(shí)參數(shù)出棧以及資源釋放等,這樣提高了程序執(zhí)行速度。
Java不支持直接聲明為內(nèi)聯(lián)函數(shù)的,如果想讓他內(nèi)聯(lián),則是由編譯器說(shuō)了算,你只能夠向編譯器提出請(qǐng)求。
final除了不能被override外,還可能實(shí)現(xiàn)內(nèi)聯(lián)。如果函數(shù)為private,則也可能是內(nèi)聯(lián)的。
總的來(lái)說(shuō),一般的函數(shù)都不會(huì)被當(dāng)做內(nèi)聯(lián)函數(shù),只有聲明了final后,編譯器才會(huì)考慮是不是要把你的函數(shù)變成內(nèi)聯(lián)函數(shù)。
內(nèi)聯(lián)不一定好,當(dāng)被指定為內(nèi)聯(lián)的方法體很大時(shí),展開(kāi)的開(kāi)銷(xiāo)可能就已經(jīng)超過(guò)了普通函數(shù)調(diào)用調(diào)用的時(shí)間,引入了內(nèi)聯(lián)反而降低了性能,因?yàn)樵谶x擇這個(gè)關(guān)鍵字應(yīng)該慎重些,不過(guò),在以后高版本的JVM中,在處理內(nèi)聯(lián)時(shí)做出了優(yōu)化,它會(huì)根據(jù)方法的規(guī)模來(lái)確定是否展開(kāi)調(diào)用。
總結(jié)
到此這篇關(guān)于JAVA深入探究之Method的Invoke方法的文章就介紹到這了,更多相關(guān)JAVA Method的Invoke方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringSecurity解決POST方式下CSRF問(wèn)題
本文主要介紹了SpringSecurity解決POST方式下CSRF問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07利用idea快速搭建一個(gè)spring-cloud(圖文)
本文主要介紹了idea快速搭建一個(gè)spring-cloud,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Java?spring?boot發(fā)送郵箱實(shí)現(xiàn)過(guò)程記錄
我們?cè)?站上注冊(cè)賬號(hào)的時(shí)候?般需要獲取驗(yàn)證碼,?這個(gè)驗(yàn)證碼?般發(fā)送在你的?機(jī)號(hào)上還有的是發(fā)送在你的郵箱中,這篇文章主要給大家介紹了關(guān)于Java?spring?boot發(fā)送郵箱實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-01-01Java中spring boot 字符串判斷是否為空方法小結(jié)
這篇文章主要介紹了Java中spring boot字符串判斷是否為空,通過(guò)安裝依賴(lài),結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11基于EasyExcel實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)入導(dǎo)出詳解
大數(shù)據(jù)的導(dǎo)入和導(dǎo)出,相信大家在日常的開(kāi)發(fā)、面試中都會(huì)遇到。本文將為大家詳細(xì)介紹一下如何利用EasyExcel實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)入導(dǎo)出,需要的可以參考一下2023-01-01解決Hibernate4執(zhí)行save()或update()無(wú)效問(wèn)題的方法
這篇文章主要為大家詳細(xì)介紹了解決Hibernate4執(zhí)行save()或update()無(wú)效問(wèn)題的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法
最近項(xiàng)目在線(xiàn)上運(yùn)行出現(xiàn)了一些難以復(fù)現(xiàn)的bug需要定位相應(yīng)api的日志,通過(guò)nginx提供的api請(qǐng)求日志難以實(shí)現(xiàn),于是在gateway通過(guò)全局過(guò)濾器記錄api請(qǐng)求日志,本文給大家介紹基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄,感興趣的朋友一起看看吧2023-11-11Idea 2020.2安裝MyBatis Log Plugin 不可用的解決方法
小編在使用時(shí)發(fā)現(xiàn)Idea 2020.2 MyBatis Log Plugin 收費(fèi)了,這個(gè)可以替代用,小編特此把解決方案分享到腳本之家平臺(tái)供大家參考,感興趣的朋友一起看看吧2020-11-11java圖形驗(yàn)證碼生成工具類(lèi) web頁(yè)面校驗(yàn)驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了java圖形驗(yàn)證碼生成工具類(lèi),web頁(yè)面校驗(yàn)驗(yàn)證碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03