关于反射调用方法的一个log
生活随笔
收集整理的這篇文章主要介紹了
关于反射调用方法的一个log
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
[Loaded sun.reflect.GeneratedMethodAccessor197 from __JVM_DefineClass__]?
請問報這個是什么意思?
這是Sun實現的Java標準庫的一個細節。下面舉例稍微講解一下。?
假如有這么一個類A:?
Java代碼??public?class?A?{?? ????public?void?foo(String?name)?{?? ????????System.out.println("Hello,?"?+?name);?? ????}?? }??
可以編寫另外一個類來反射調用A上的方法:?
Java代碼??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類上不會有對類A的符號依賴——也就是說在加載并初始化TestClassLoad類時不需要關心類A的存在與否,而是等到main()方法執行到調用Class.forName()時才試圖對類A做動態加載;這里用的是一個參數版的forName(),也就是使用當前方法所在類的ClassLoader來加載,并且初始化新加載的類。……好吧這個細節跟主題沒啥關系。?
回到主題。這次我的測試環境是Sun的JDK 1.6.0 update 13 build 03。編譯上述代碼,并在執行TestClassLoad時加入-XX:+TraceClassLoading參數(或者-verbose:class或者直接-verbose都行),如下:?
Command prompt代碼??java?-XX:+TraceClassLoading?TestClassLoad??
可以看到輸出了一大堆log,把其中相關的部分截取出來如下:(完整的log可以從附件下載)?
Log代碼??[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次反射調用A.foo()方法并沒有什么稀奇的地方,但在第16次反射調用時似乎有什么東西被觸發了,導致JVM新加載了一堆類,其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]這么一行。這是哪里來的呢??
先來看看JDK里Method.invoke()是怎么實現的。?
java.lang.reflect.Method:?
Java代碼??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()實際上并不是自己實現的反射調用邏輯,而是委托給sun.reflect.MethodAccessor來處理。?
每個實際的Java方法只有一個對應的Method對象作為root,。這個root是不會暴露給用戶的,而是每次在通過反射獲取Method對象時新創建Method對象把root包裝起來再給用戶。在第一次調用一個實際Java方法對應得Method對象的invoke()方法之前,實現調用邏輯的MethodAccessor對象還沒創建;等第一次調用時才新創建MethodAccessor并更新給root,然后調用MethodAccessor.invoke()真正完成反射調用。?
那么MethodAccessor是啥呢??
sun.reflect.MethodAccessor:?
Java代碼??public?interface?MethodAccessor?{?? ????/**?Matches?specification?in?{@link?java.lang.reflect.Method}?*/?? ????public?Object?invoke(Object?obj,?Object[]?args)?? ????????throws?IllegalArgumentException,?InvocationTargetException;?? }??
可以看到它只是一個單方法接口,其invoke()方法與Method.invoke()的對應。?
創建MethodAccessor實例的是ReflectionFactory。?
sun.reflect.ReflectionFactory:?
Java代碼??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;?? ????????}?? ????}?? }??
這里就可以看到有趣的地方了。如注釋所述,實際的MethodAccessor實現有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說性能較好;native版本正好相反,啟動時相對較快,但運行時間長了之后速度就比不過Java版了。這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內聯,于是運行時間長了之后反而是托管版本的代碼更快些。?
為了權衡兩個版本的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調用時,開頭若干次使用native版,等反射調用次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的字節碼,以后對該Java方法的反射調用就會使用Java版。?
Sun的JDK是從1.4系開始采用這種優化的,主要作者是 Ken Russell ?
上面看到了ReflectionFactory.newMethodAccessor()生產MethodAccessor的邏輯,在“開頭若干次”時用到的DelegatingMethodAccessorImpl代碼如下:?
sun.reflect.DelegatingMethodAccessorImpl:?
Java代碼??/**?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:?
Java代碼??/**?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()方法被調用時,都會增加一個調用次數計數器,看超過閾值沒有;一旦超過,則調用MethodAccessorGenerator.generateMethod()來生成Java版的MethodAccessor的實現類,并且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。后續經由DelegatingMethodAccessorImpl.invoke()調用到的就是Java版的實現了。?
注意到關鍵的invoke0()方法是個native方法。它在HotSpot VM里是由JVM_InvokeMethod()函數所支持的:?
C代碼??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);?? }??
C++代碼??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():?
C++代碼??//?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的內部了,本文就在這里打住吧。有同學有興趣深究的話以后可以再寫一篇討論native版的實現。?
回到Java的一側。MethodAccessorGenerator長啥樣呢?由于代碼太長,這里就不完整貼了,有興趣的可以到OpenJDK 6的Mercurial倉庫看: OpenJDK 6 build 17的MethodAccessorGenerator 。它的基本工作就是在內存里生成新的專用Java類,并將其加載。就貼這么一個方法:?
Java代碼??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;?? ????}?? }??
去閱讀源碼的話,可以看到MethodAccessorGenerator是如何一點點把Java版的MethodAccessor實現類生產出來的。也可以看到GeneratedMethodAccessor+數字這種名字是從哪里來的了,就在上面的generateName()方法里。?
對本文開頭的例子的A.foo(),生成的Java版MethodAccessor大致如下:?
Java代碼??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);?? ????????}?? ????}?? }??
就反射調用而言,這個invoke()方法非常干凈(然而就“正常調用”而言這額外開銷還是明顯的)。注意到參數數組被拆開了,把每個參數都恢復到原本沒有被Object[]包裝前的樣子,然后對目標方法做正常的invokevirtual調用。由于在生成代碼時已經循環遍歷過參數類型的數組,生成出來的代碼里就不再包含循環了。?
當該反射調用成為熱點時,它甚至可以被內聯到靠近Method.invoke()的一側,大大降低了反射調用的開銷。而native版的反射調用則無法被有效內聯,因而調用開銷無法隨程序的運行而降低。?
雖說Sun的JDK這種實現方式使得反射調用方法成本比以前降低了很多,但Method.invoke()本身要用數組包裝參數;而且每次調用都必須檢查方法的可見性(在Method.invoke()里),也必須檢查每個實際參數與形式參數的類型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里);而且Method.invoke()就像是個獨木橋一樣,各處的反射調用都要擠過去,在調用點上收集到的類型信息就會很亂,影響內聯程序的判斷,使得Method.invoke()自身難以被內聯到調用方。?
相比之下 JDK 7里新的MethodHandle 則更有潛力,在其功能完全實現后能達到比普通反射調用方法更高的性能。在使用MethodHandle來做反射調用時,MethodHandle.invoke()的形式參數與返回值類型都是準確的,所以只需要在鏈接方法的時候才需要檢查類型的匹配性,而不必在每次調用時都檢查。而且MethodHandle是不可變值,在創建后其內部狀態就不會再改變了;JVM可以利用這個知識而放心的對它做激進優化,例如將實際的調用目標內聯到做反射調用的一側。?
到本來Java的安全機制使得不同類之間不是任意信息都可見,但Sun的JDK里開了個口,有一個標記類專門用于開后門:?
Java代碼??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?{?? }??
那個"__JVM_DefineClass__"的來源是這里:?
src/share/vm/prims/jvm.cpp?
C++代碼??//?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__";??
請問報這個是什么意思?
這是Sun實現的Java標準庫的一個細節。下面舉例稍微講解一下。?
假如有這么一個類A:?
Java代碼??
可以編寫另外一個類來反射調用A上的方法:?
Java代碼??
注意到TestClassLoad類上不會有對類A的符號依賴——也就是說在加載并初始化TestClassLoad類時不需要關心類A的存在與否,而是等到main()方法執行到調用Class.forName()時才試圖對類A做動態加載;這里用的是一個參數版的forName(),也就是使用當前方法所在類的ClassLoader來加載,并且初始化新加載的類。……好吧這個細節跟主題沒啥關系。?
回到主題。這次我的測試環境是Sun的JDK 1.6.0 update 13 build 03。編譯上述代碼,并在執行TestClassLoad時加入-XX:+TraceClassLoading參數(或者-verbose:class或者直接-verbose都行),如下:?
Command prompt代碼??
可以看到輸出了一大堆log,把其中相關的部分截取出來如下:(完整的log可以從附件下載)?
Log代碼??
可以看到前15次反射調用A.foo()方法并沒有什么稀奇的地方,但在第16次反射調用時似乎有什么東西被觸發了,導致JVM新加載了一堆類,其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]這么一行。這是哪里來的呢??
先來看看JDK里Method.invoke()是怎么實現的。?
java.lang.reflect.Method:?
Java代碼??
可以看到Method.invoke()實際上并不是自己實現的反射調用邏輯,而是委托給sun.reflect.MethodAccessor來處理。?
每個實際的Java方法只有一個對應的Method對象作為root,。這個root是不會暴露給用戶的,而是每次在通過反射獲取Method對象時新創建Method對象把root包裝起來再給用戶。在第一次調用一個實際Java方法對應得Method對象的invoke()方法之前,實現調用邏輯的MethodAccessor對象還沒創建;等第一次調用時才新創建MethodAccessor并更新給root,然后調用MethodAccessor.invoke()真正完成反射調用。?
那么MethodAccessor是啥呢??
sun.reflect.MethodAccessor:?
Java代碼??
可以看到它只是一個單方法接口,其invoke()方法與Method.invoke()的對應。?
創建MethodAccessor實例的是ReflectionFactory。?
sun.reflect.ReflectionFactory:?
Java代碼??
這里就可以看到有趣的地方了。如注釋所述,實際的MethodAccessor實現有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說性能較好;native版本正好相反,啟動時相對較快,但運行時間長了之后速度就比不過Java版了。這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內聯,于是運行時間長了之后反而是托管版本的代碼更快些。?
為了權衡兩個版本的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調用時,開頭若干次使用native版,等反射調用次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的字節碼,以后對該Java方法的反射調用就會使用Java版。?
Sun的JDK是從1.4系開始采用這種優化的,主要作者是 Ken Russell ?
上面看到了ReflectionFactory.newMethodAccessor()生產MethodAccessor的邏輯,在“開頭若干次”時用到的DelegatingMethodAccessorImpl代碼如下:?
sun.reflect.DelegatingMethodAccessorImpl:?
Java代碼??
這是一個間接層,方便在native與Java版的MethodAccessor之間實現切換。?
然后下面就是native版MethodAccessor的Java一側的聲明:?
sun.reflect.NativeMethodAccessorImpl:?
Java代碼??
每次NativeMethodAccessorImpl.invoke()方法被調用時,都會增加一個調用次數計數器,看超過閾值沒有;一旦超過,則調用MethodAccessorGenerator.generateMethod()來生成Java版的MethodAccessor的實現類,并且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。后續經由DelegatingMethodAccessorImpl.invoke()調用到的就是Java版的實現了。?
注意到關鍵的invoke0()方法是個native方法。它在HotSpot VM里是由JVM_InvokeMethod()函數所支持的:?
C代碼??
C++代碼??
其中的關鍵又是Reflection::invoke_method():?
C++代碼??
再下去就深入到HotSpot VM的內部了,本文就在這里打住吧。有同學有興趣深究的話以后可以再寫一篇討論native版的實現。?
回到Java的一側。MethodAccessorGenerator長啥樣呢?由于代碼太長,這里就不完整貼了,有興趣的可以到OpenJDK 6的Mercurial倉庫看: OpenJDK 6 build 17的MethodAccessorGenerator 。它的基本工作就是在內存里生成新的專用Java類,并將其加載。就貼這么一個方法:?
Java代碼??
去閱讀源碼的話,可以看到MethodAccessorGenerator是如何一點點把Java版的MethodAccessor實現類生產出來的。也可以看到GeneratedMethodAccessor+數字這種名字是從哪里來的了,就在上面的generateName()方法里。?
對本文開頭的例子的A.foo(),生成的Java版MethodAccessor大致如下:?
Java代碼??
就反射調用而言,這個invoke()方法非常干凈(然而就“正常調用”而言這額外開銷還是明顯的)。注意到參數數組被拆開了,把每個參數都恢復到原本沒有被Object[]包裝前的樣子,然后對目標方法做正常的invokevirtual調用。由于在生成代碼時已經循環遍歷過參數類型的數組,生成出來的代碼里就不再包含循環了。?
當該反射調用成為熱點時,它甚至可以被內聯到靠近Method.invoke()的一側,大大降低了反射調用的開銷。而native版的反射調用則無法被有效內聯,因而調用開銷無法隨程序的運行而降低。?
雖說Sun的JDK這種實現方式使得反射調用方法成本比以前降低了很多,但Method.invoke()本身要用數組包裝參數;而且每次調用都必須檢查方法的可見性(在Method.invoke()里),也必須檢查每個實際參數與形式參數的類型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里);而且Method.invoke()就像是個獨木橋一樣,各處的反射調用都要擠過去,在調用點上收集到的類型信息就會很亂,影響內聯程序的判斷,使得Method.invoke()自身難以被內聯到調用方。?
相比之下 JDK 7里新的MethodHandle 則更有潛力,在其功能完全實現后能達到比普通反射調用方法更高的性能。在使用MethodHandle來做反射調用時,MethodHandle.invoke()的形式參數與返回值類型都是準確的,所以只需要在鏈接方法的時候才需要檢查類型的匹配性,而不必在每次調用時都檢查。而且MethodHandle是不可變值,在創建后其內部狀態就不會再改變了;JVM可以利用這個知識而放心的對它做激進優化,例如將實際的調用目標內聯到做反射調用的一側。?
到本來Java的安全機制使得不同類之間不是任意信息都可見,但Sun的JDK里開了個口,有一個標記類專門用于開后門:?
Java代碼??
那個"__JVM_DefineClass__"的來源是這里:?
src/share/vm/prims/jvm.cpp?
C++代碼??
總結
以上是生活随笔為你收集整理的关于反射调用方法的一个log的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大量DelegatingClassLoa
- 下一篇: Presenting the Perma