<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【108期】面試官:你真的知道 Java 類是如何被加載的嗎?

          共 16445字,需瀏覽 33分鐘

           ·

          2020-12-31 00:36

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
          關(guān)注


          閱讀本文大概需要 11 分鐘。

          來自:https://yq.aliyun.com/articles/710407

          一:前言

          最近給一個非Java方向的朋友講了下雙親委派模型,朋友讓我寫篇文章深度研究下JVM的ClassLoader,我確實(shí)也好久沒寫JVM相關(guān)的文章了,有點(diǎn)手癢癢,涂了皮炎平也抑制不住。
          我在向朋友解釋的時候是這么說的:雙親委派模型中,ClassLoader在加載類的時候,會先交由它的父ClassLoader加載,只有當(dāng)父ClassLoader加載失敗的情況下,才會嘗試自己去加載。這樣可以實(shí)現(xiàn)部分類的復(fù)用,又可以實(shí)現(xiàn)部分類的隔離,因?yàn)椴煌珻lassLoader加載的類是互相隔離的。
          不過貿(mào)然的向別人解釋雙親委派模型是不妥的,如果在不了解JVM的類加載機(jī)制的情況下,又如何能很好的理解“不同ClassLoader加載的類是互相隔離的”這句話呢?所以為了理解雙親委派,最好的方式,就是先了解下ClassLoader的加載流程。

          二:Java 類是如何被加載的

          2.1:何時加載類

          我們首先要清楚的是,Java類何時會被加載?
          《深入理解Java虛擬機(jī)》給出的答案是:
          • 遇到new、getstatic、putstatic 等指令時。

          • 對類進(jìn)行反射調(diào)用的時候。

          • 初始化某個類的子類的時候。

          • 虛擬機(jī)啟動時會先加載設(shè)置的程序主類。

          • 使用JDK 1.7 的動態(tài)語言支持的時候。

          其實(shí)要我說,最通俗易懂的答案就是:當(dāng)運(yùn)行過程中需要這個類的時候。
          那么我們不妨就從如何加載類開始說起。

          2.2:怎么加載類

          利用ClassLoader加載類很簡單,直接調(diào)用ClassLoder的loadClass()方法即可,我相信大家都會,但是還是要舉個栗子:
          public?class?Test?{
          ????public?static?void?main(String[]?args)?throws?ClassNotFoundException?{
          ????????Test.class.getClassLoader().loadClass("com.wangxiandeng.test.Dog");
          ????}
          }
          上面這段代碼便實(shí)現(xiàn)了讓ClassLoader去加載 “com.wangxiandeng.test.Dog” 這個類,是不是 so easy。但是JDK 提供的 API 只是冰山一角,看似很簡單的一個調(diào)用,其實(shí)隱藏了非常多的細(xì)節(jié),我這個人吧,最喜歡做的就是去揭開 API 的封裝,一探究竟。

          2.3:JVM 是怎么加載類的

          JVM 默認(rèn)用于加載用戶程序的ClassLoader為AppClassLoader,不過無論是什么ClassLoader,它的根父類都是java.lang.ClassLoader。在上面那個例子中,loadClass()方法最終會調(diào)用到ClassLoader.definClass1()中,這是一個 Native 方法。
          static?native?Class?defineClass1(ClassLoader?loader,?String?name,?byte[]?b,?int?off,?int?len,
          ????????????????????????????????????????ProtectionDomain?pd,?String?source);?
          看到 Native 方法莫心慌,不要急,打開OpenJDK源碼,我等繼續(xù)走馬觀花便是!
          definClass1()對應(yīng)的 JNI 方法為 Java_java_lang_ClassLoader_defineClass1()
          JNIEXPORT?jclass?JNICALL
          Java_java_lang_ClassLoader_defineClass1(JNIEnv?*env,
          ????????????????????????????????????????jclass?cls,
          ????????????????????????????????????????jobject?loader,
          ????????????????????????????????????????jstring?name,
          ????????????????????????????????????????jbyteArray?data,
          ????????????????????????????????????????jint?offset,
          ????????????????????????????????????????jint?length,
          ????????????????????????????????????????jobject?pd,
          ????????????????????????????????????????jstring?source)
          {
          ????......
          ????result?=?JVM_DefineClassWithSource(env,?utfName,?loader,?body,?length,?pd,?utfSource);
          ????......
          ????return?result;
          }
          Java_java_lang_ClassLoader_defineClass1 主要是調(diào)用了JVM_DefineClassWithSource()加載類,跟著源碼往下走,會發(fā)現(xiàn)最終調(diào)用的是 jvm.cpp 中的 jvm_define_class_common()方法。
          static?jclass?jvm_define_class_common(JNIEnv?*env,?const?char?*name,
          ??????????????????????????????????????jobject?loader,?const?jbyte?*buf,
          ??????????????????????????????????????jsize?len,?jobject?pd,?const?char?*source,
          ??????????????????????????????????????TRAPS)?{
          ??......
          ??ClassFileStream?st((u1*)buf,?len,?source,?ClassFileStream::verify);
          ??Handle?class_loader?(THREAD,?JNIHandles::resolve(loader));
          ??if?(UsePerfData)?{
          ????is_lock_held_by_thread(class_loader,
          ???????????????????????????ClassLoader::sync_JVMDefineClassLockFreeCounter(),
          ???????????????????????????THREAD);
          ??}
          ??Handle?protection_domain?(THREAD,?JNIHandles::resolve(pd));
          ??Klass*?k?=?SystemDictionary::resolve_from_stream(class_name,
          ???????????????????????????????????????????????????class_loader,
          ???????????????????????????????????????????????????protection_domain,
          ???????????????????????????????????????????????????&st,
          ???????????????????????????????????????????????????CHECK_NULL);
          ??......

          ??return?(jclass)?JNIHandles::make_local(env,?k->java_mirror());
          }
          上面這段邏輯主要就是利用 ClassFileStream 將要加載的class文件轉(zhuǎn)成文件流,然后調(diào)用SystemDictionary::resolve_from_stream(),生成 Class 在 JVM 中的代表:Klass。
          對于Klass,大家可能不太熟悉,但是在這里必須得了解下。說白了,它就是JVM 用來定義一個Java Class 的數(shù)據(jù)結(jié)構(gòu)。不過Klass只是一個基類,Java Class 真正的數(shù)據(jù)結(jié)構(gòu)定義在 InstanceKlass中。
          class?InstanceKlass:?public?Klass?{

          ?protected:

          ??Annotations*????_annotations;
          ??......
          ??ConstantPool*?_constants;
          ??......
          ??Array*?_inner_classes;
          ??......
          ??Array*?_methods;
          ??Array*?_default_methods;
          ??......
          ??Array*??????_fields;
          }
          可見 InstanceKlass 中記錄了一個 Java 類的所有屬性,包括注解、方法、字段、內(nèi)部類、常量池等信息。這些信息本來被記錄在Class文件中,所以說,InstanceKlass就是一個Java Class 文件被加載到內(nèi)存后的形式。
          再回到上面的類加載流程中,這里調(diào)用了 SystemDictionary::resolve_from_stream(),將 Class 文件加載成內(nèi)存中的 Klass。
          resolve_from_stream() 便是重中之重!主要邏輯有下面幾步:
          1:判斷是否允許并行加載類,并根據(jù)判斷結(jié)果進(jìn)行加鎖。
          bool?DoObjectLock?=?true;
          if?(is_parallelCapable(class_loader))?{
          ??DoObjectLock?=?false;
          }
          ClassLoaderData*?loader_data?=?register_loader(class_loader,?CHECK_NULL);
          Handle?lockObject?=?compute_loader_lock_object(class_loader,?THREAD);
          check_loader_lock_contention(lockObject,?THREAD);
          ObjectLocker?ol(lockObject,?THREAD,?DoObjectLock);
          如果允許并行加載,則不會對ClassLoader進(jìn)行加鎖,只對SystemDictionary加鎖。否則,便會利用 ObjectLocker 對ClassLoader 加鎖,保證同一個ClassLoader在同一時刻只能加載一個類。ObjectLocker 會在其構(gòu)造函數(shù)中獲取鎖,并在析構(gòu)函數(shù)中釋放鎖。
          允許并行加載的好處便是精細(xì)化了鎖粒度,這樣可以在同一時刻加載多個Class文件。
          2:解析文件流,生成 InstanceKlass。
          InstanceKlass*?k?=?NULL;

          k?=?KlassFactory::create_from_stream(st,
          ?????????????????????????????????????????class_name,
          ?????????????????????????????????????????loader_data,
          ?????????????????????????????????????????protection_domain,
          ?????????????????????????????????????????NULL,?//?host_klass
          ?????????????????????????????????????????NULL,?//?cp_patches
          ?????????????????????????????????????????CHECK_NULL);
          3:利用SystemDictionary注冊生成的 Klass。
          SystemDictionary 是用來幫助保存 ClassLoader 加載過的類信息的。準(zhǔn)確點(diǎn)說,SystemDictionary并不是一個容器,真正用來保存類信息的容器是 Dictionary,每個ClassLoaderData 中都保存著一個私有的 Dictionary,而 SystemDictionary 只是一個擁有很多靜態(tài)方法的工具類而已。
          我們來看看注冊的代碼:
          if?(is_parallelCapable(class_loader))?{
          ??InstanceKlass*?defined_k?=?find_or_define_instance_class(h_name,?class_loader,?k,?THREAD);
          ??if?(!HAS_PENDING_EXCEPTION?&&?defined_k?!=?k)?{
          ????//?If?a?parallel?capable?class?loader?already?defined?this?class,?register?'k'?for?cleanup.
          ????assert(defined_k?!=?NULL,?"Should?have?a?klass?if?there's?no?exception");
          ????loader_data->add_to_deallocate_list(k);
          ????k?=?defined_k;
          ??}
          }?else?{
          ??define_instance_class(k,?THREAD);
          }
          如果允許并行加載,那么前面就不會對ClassLoader加鎖,所以在同一時刻,可能對同一Class文件加載了多次。但是同一Class在同一ClassLoader中必須保持唯一性,所以這里會先利用 SystemDictionary 查詢 ClassLoader 是否已經(jīng)加載過相同 Class。
          如果已經(jīng)加載過,那么就將當(dāng)前線程剛剛加載的InstanceKlass加入待回收列表,并將 InstanceKlass* k 重新指向利用SystemDictionary查詢到的 InstanceKlass。
          如果沒有查詢到,那么就將剛剛加載的 InstanceKlass 注冊到 ClassLoader的 Dictionary 中 中。
          雖然并行加載不會鎖住ClassLoader,但是會在注冊 InstanceKlass 時對 SystemDictionary 加鎖,所以不需要擔(dān)心InstanceKlass 在注冊時的并發(fā)操作。
          如果禁止了并行加載,那么直接利用SystemDictionary將 InstanceKlass 注冊到 ClassLoader的 Dictionary 中即可。
          resolve_from_stream()的主要流程就是上面三步,很明顯,最重要的是第二步,從文件流生成InstanceKlass。
          生成InstanceKlass 調(diào)用的是 KlassFactory::create_from_stream()方法,它的主要邏輯就是下面這段代碼。
          ClassFileParser?parser(stream,
          ???????????????????????name,
          ???????????????????????loader_data,
          ???????????????????????protection_domain,
          ???????????????????????host_klass,
          ???????????????????????cp_patches,
          ???????????????????????ClassFileParser::BROADCAST,?//?publicity?level
          ???????????????????????CHECK_NULL)
          ;

          InstanceKlass*?result?=?parser.create_instance_klass(old_stream?!=?stream,?CHECK_NULL);
          原來 ClassFileParser 才是真正的主角??!它才是將Class文件升華成InstanceKlass的幕后大佬!

          2.4:不得不說的ClassFileParser

          ClassFileParser 加載Class文件的入口便是 create_instance_klass()。顧名思義,用來創(chuàng)建InstanceKlass的。
          create_instance_klass()主要就干了兩件事:
          (1):為 InstanceKlass 分配內(nèi)存
          InstanceKlass*?const?ik?=
          ????InstanceKlass::allocate_instance_klass(*this,?CHECK_NULL);
          (2):分析Class文件,填充 InstanceKlass 內(nèi)存區(qū)域
          fill_instance_klass(ik, changed_by_loadhook, CHECK_NULL);
          我們先來說道說道第一件事,為 InstanceKlass 分配內(nèi)存。
          內(nèi)存分配代碼如下:
          const?int?size?=?InstanceKlass::size(parser.vtable_size(),
          ???????????????????????????????????????parser.itable_size(),
          ???????????????????????????????????????nonstatic_oop_map_size(parser.total_oop_map_count()),
          ???????????????????????????????????????parser.is_interface(),
          ???????????????????????????????????????parser.is_anonymous(),
          ???????????????????????????????????????should_store_fingerprint(parser.is_anonymous()));
          ClassLoaderData*?loader_data?=?parser.loader_data();
          InstanceKlass*?ik;
          ik?=?new?(loader_data,?size,?THREAD)?InstanceKlass(parser,?InstanceKlass::_misc_kind_other);
          這里首先計算了InstanceKlass在內(nèi)存中的大小,要知道,這個大小在Class 文件編譯后就被確定了。
          然后便 new 了一個新的 InstanceKlass 對象。這里并不是簡單的在堆上分配內(nèi)存,要注意的是Klass 對 new 操作符進(jìn)行了重載:
          void*?Klass::operator?new(size_t?size,?ClassLoaderData*?loader_data,?size_t?word_size,?TRAPS)?throw()?{
          ??return?Metaspace::allocate(loader_data,?word_size,?MetaspaceObj::ClassType,?THREAD);
          }
          分配 InstanceKlass 的時候調(diào)用了 Metaspace::allocate():
          ?????????????????????????????MetaspaceObj::Type?type,?TRAPS)?{
          ??......
          ??MetadataType?mdtype?=?(type?==?MetaspaceObj::ClassType)???ClassType?:?NonClassType;
          ??......
          ??MetaWord*?result?=?loader_data->metaspace_non_null()->allocate(word_size,?mdtype);
          ??......
          ??return?result;
          }
          由此可見,InstanceKlass 是分配在 ClassLoader的 Metaspace(元空間) 的方法區(qū)中。從 JDK8 開始,HotSpot 就沒有了永久代,類都分配在 Metaspace 中。Metaspace 和永久代不一樣,采用的是 Native Memory,永久代由于受限于 MaxPermSize,所以當(dāng)內(nèi)存不夠時會內(nèi)存溢出。
          分配完 InstanceKlass 內(nèi)存后,便要著手第二件事,分析Class文件,填充 InstanceKlass 內(nèi)存區(qū)域。
          ClassFileParser 在構(gòu)造的時候就會開始分析Class文件,所以fill_instance_klass()中只需要填充即可。填充結(jié)束后,還會調(diào)用 java_lang_Class::create_mirror()創(chuàng)建 InstanceKlass 在Java 層的 Class 對象。
          void?ClassFileParser::fill_instance_klass(InstanceKlass*?ik,?bool?changed_by_loadhook,?TRAPS)?{
          ??.....
          ??ik->set_class_loader_data(_loader_data);
          ??ik->set_nonstatic_field_size(_field_info->nonstatic_field_size);
          ??ik->set_has_nonstatic_fields(_field_info->has_nonstatic_fields);
          ??ik->set_static_oop_field_count(_fac->count[STATIC_OOP]);
          ??ik->set_name(_class_name);
          ??......

          ??java_lang_Class::create_mirror(ik,
          ?????????????????????????????????Handle(THREAD,?_loader_data->class_loader()),
          ?????????????????????????????????module_handle,
          ?????????????????????????????????_protection_domain,
          ?????????????????????????????????CHECK);
          }
          到這兒,Class文件已經(jīng)完成了華麗的轉(zhuǎn)身,由冷冰冰的二進(jìn)制文件,變成了內(nèi)存中充滿生命力的InstanceKlass。

          三:再談雙親委派

          如果你耐心的看完了上面的源碼分析,你一定對 “不同ClassLoader加載的類是互相隔離的” 這句話的理解又上了一個臺階。
          我們總結(jié)下:每個ClassLoader都有一個 Dictionary 用來保存它所加載的InstanceKlass信息。并且,每個 ClassLoader 通過鎖,保證了對于同一個Class,它只會注冊一份 InstanceKlass 到自己的 Dictionary 。
          正式由于上面這些原因,如果所有的 ClassLoader 都由自己去加載 Class 文件,就會導(dǎo)致對于同一個Class文件,存在多份InstanceKlass,所以即使是同一個Class文件,不同InstanceKlasss 衍生出來的實(shí)例類型也是不一樣的。
          舉個栗子,我們自定義一個 ClassLoader,用來打破雙親委派模型:
          public?class?CustomClassloader?extends?URLClassLoader?{

          ????public?CustomClassloader(URL[]?urls)?{
          ????????super(urls);
          ????}

          ????@Override
          ????protected?Class?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{
          ????????if?(name.startsWith("com.wangxiandeng"))?{
          ????????????return?findClass(name);
          ????????}
          ????????return?super.loadClass(name,?resolve);
          ????}
          }
          再嘗試加載Studen類,并實(shí)例化:
          public?class?Test?{

          ????public?static?void?main(String[]?args)?throws?Exception?{
          ????????URL?url[]?=?new?URL[1];
          ????????url[0]?=?Thread.currentThread().getContextClassLoader().getResource("");

          ????????CustomClassloader?customClassloader?=?new?CustomClassloader(url);
          ????????Class?clazz?=?customClassloader.loadClass("com.wangxiandeng.Student");

          ????????Student?student?=?(Student)?clazz.newInstance();
          ????}
          }
          運(yùn)行后便會拋出類型強(qiáng)轉(zhuǎn)異常:
          Exception?in?thread?"main"?java.lang.ClassCastException:
          ??????com.wangxiandeng.Student?cannot?be?cast?to?com.wangxiandeng.Student
          為什么呢?
          因?yàn)閷?shí)例化的Student對象所屬的 InstanceKlass 是由CustomClassLoader加載生成的,而我們要強(qiáng)轉(zhuǎn)的類型Student.Class 對應(yīng)的 InstanceKlass 是由系統(tǒng)默認(rèn)的ClassLoader生成的,所以本質(zhì)上它們就是兩個毫無關(guān)聯(lián)的InstanceKlass,當(dāng)然不能強(qiáng)轉(zhuǎn)。
          有同學(xué)問到:為什么“強(qiáng)轉(zhuǎn)的類型Student.Class 對應(yīng)的 InstanceKlass 是由系統(tǒng)默認(rèn)的ClassLoader生成的”?
          其實(shí)很簡單,我們反編譯下字節(jié)碼:
          ??public?static?void?main(java.lang.String[])?throws?java.lang.Exception;
          ????descriptor:?([Ljava/lang/String;)V
          ????flags:?ACC_PUBLIC,?ACC_STATIC
          ????Code:
          ??????stack=4,?locals=5,?args_size=1
          ?????????0:?iconst_1
          ?????????1:?anewarray?????#2??????????????????//?class?java/net/URL
          ?????????4:?astore_1
          ?????????5:?aload_1
          ?????????6:?iconst_0
          ?????????7:?invokestatic??#3??????????????????//?Method?java/lang/Thread.currentThread:()Ljava/lang/Thread;
          ????????10:?invokevirtual?#4??????????????????//?Method?java/lang/Thread.getContextClassLoader:()Ljava/lang/ClassLoader;
          ????????13:?ldc???????????#5??????????????????//?String
          ????????15:?invokevirtual?#6??????????????????//?Method?java/lang/ClassLoader.getResource:(Ljava/lang/String;)Ljava/net/URL;
          ????????18:?aastore
          ????????19:?new???????????#7??????????????????//?class?com/wangxiandeng/classloader/CustomClassloader
          ????????22:?dup
          ????????23:?aload_1
          ????????24:?invokespecial?#8??????????????????//?Method?com/wangxiandeng/classloader/CustomClassloader."":([Ljava/net/URL;)V
          ????????27:?astore_2
          ????????28:?aload_2
          ????????29:?ldc???????????#9??????????????????//?String?com.wangxiandeng.Student
          ????????31:?invokevirtual?#10?????????????????//?Method?com/wangxiandeng/classloader/CustomClassloader.loadClass:(Ljava/lang/String;)Ljava/lang/Class;
          ????????34:?astore_3
          ????????35:?aload_3
          ????????36:?invokevirtual?#11?????????????????//?Method?java/lang/Class.newInstance:()Ljava/lang/Object;
          ????????39:?checkcast?????#12?????????????????//?class?com/wangxiandeng/Student
          ????????42:?astore????????4
          ????????44:?return
          可以看到在利用加載的Class初始化實(shí)例后,調(diào)用了 checkcast 進(jìn)行類型轉(zhuǎn)化,checkcast 后的操作數(shù) #12 即為Student這個類在常量池中的索引:#12 = Class #52 // com/wangxiandeng/Student
          下面我們可以看看 checkcast 在HotSpot中的實(shí)現(xiàn)。
          HotSpot 目前有三種字節(jié)碼執(zhí)行引擎,目前采用的是模板解釋器,早期的HotSpot采用的是字節(jié)碼解釋器。模板解釋器對于指令的執(zhí)行都是用匯編寫的,而字節(jié)碼解釋器采用的C++進(jìn)行的翻譯,為了看起來比較舒服,我們就不看匯編了,直接看字節(jié)碼解釋器就行了。如果你的匯編功底很好,當(dāng)然也可以直接看模板解釋器。
          廢話不多說,我們來看看字節(jié)碼解釋器對于checkcast的實(shí)現(xiàn),代碼在 bytecodeInterpreter.cpp 中
          CASE(_checkcast):
          ????if?(STACK_OBJECT(-1)?!=?NULL)?{
          ??????VERIFY_OOP(STACK_OBJECT(-1));
          ??????//?拿到 checkcast 指令后的操作數(shù),本例子中即 Student.Class 在常量池中的索引:#12
          ??????u2?index?=?Bytes::get_Java_u2(pc+1);

          ??????//?如果常量池還沒有解析,先進(jìn)行解析,即將常量池中的符號引用替換成直接引用,
          ??????//此時就會觸發(fā)Student.Class?的加載
          ??????if?(METHOD->constants()->tag_at(index).is_unresolved_klass())?{
          ????????CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD),?handle_exception);
          ??????}
          ??????//?獲取上一步系統(tǒng)加載的Student.Class?對應(yīng)的?InstanceKlass
          ??????Klass*?klassOf?=?(Klass*)?METHOD->constants()->resolved_klass_at(index);
          ??????//?獲取要強(qiáng)轉(zhuǎn)的對象的實(shí)際類型,即我們自己手動加載的Student.Class?對應(yīng)的?InstanceKlass
          ??????Klass*?objKlass?=?STACK_OBJECT(-1)->klass();?//?ebx

          ??????//?現(xiàn)在就比較簡單了,直接看看上面的兩個InstanceKlass指針內(nèi)容是否相同
          ??????//?不同的情況下則判斷是否存在繼承關(guān)系
          ??????if?(objKlass?!=?klassOf?&&?!objKlass->is_subtype_of(klassOf))?{
          ????????//?Decrement?counter?at?checkcast.
          ????????BI_PROFILE_SUBTYPECHECK_FAILED(objKlass);
          ????????ResourceMark?rm(THREAD);
          ????????char*?message?=?SharedRuntime::generate_class_cast_message(
          ??????????objKlass,?klassOf);
          ????????VM_JAVA_ERROR(vmSymbols::java_lang_ClassCastException(),?message,?note_classCheck_trap);
          ??????}
          ??????//?Profile?checkcast?with?null_seen?and?receiver.
          ??????BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/false,?objKlass);
          ????}?else?{
          ??????//?Profile?checkcast?with?null_seen?and?receiver.
          ??????BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/true,?NULL);
          ????}
          通過對上面代碼的分析,我相信大家已經(jīng)理解了 “強(qiáng)轉(zhuǎn)的類型Student.Class 對應(yīng)的 InstanceKlass 是由系統(tǒng)默認(rèn)的ClassLoader生成的” 這句話了。
          雙親委派的好處是盡量保證了同一個Class文件只會生成一個InstanceKlass,但是某些情況,我們就不得不去打破雙親委派了,比如我們想實(shí)現(xiàn)Class隔離的時候。
          回復(fù)下簫陌同學(xué)的問題:
          //?如果常量池還沒有解析,先進(jìn)行解析,即將常量池中的符號引用替換成直接引用,
          //此時就會觸發(fā)Student.Class?的加載
          if?(METHOD->constants()->tag_at(index).is_unresolved_klass())?{
          CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD),?handle_exception);
          }
          請問,為何這里會重新加載Student.Class?jvm是不是有自己的class加載鏈路,然后系統(tǒng)循著鏈路去查找class是否已經(jīng)被加載?那該怎么把自定義的CustomClassloader 加到這個查詢鏈路中去呢?
          第一種方法:設(shè)置啟動參數(shù) java -Djava.system.class.loader
          第二種方法:利用Thread.setContextClassLoder
          這里就有點(diǎn)技巧了,看下代碼:
          public?class?Test?{

          ????public?static?void?main(String[]?args)?throws?Exception?{
          ????????URL?url[]?=?new?URL[1];
          ????????url[0]?=?Thread.currentThread().getContextClassLoader().getResource("");
          ????????final?CustomClassloader?customClassloader?=?new?CustomClassloader(url);
          ????????Thread.currentThread().setContextClassLoader(customClassloader);
          ????????Class?clazz?=?customClassloader.loadClass("com.wangxiandeng.ClassTest");
          ????????Object?object?=?clazz.newInstance();
          ????????Method?method?=?clazz.getDeclaredMethod("test");
          ????????method.invoke(object);
          ????}
          }
          public?class?ClassTest?{

          ????public?void?test()?throws?Exception{
          ????????Class?clazz?=?Thread.currentThread().getContextClassLoader().loadClass("com.wangxiandeng.Student");
          ????????Student?student?=?(Student)?clazz.newInstance();
          ????????System.out.print(student.getClass().getClassLoader());

          ????}
          }
          要注意的是在設(shè)置線程的ClassLoader后,并不是直接調(diào)用 new ClassTest().test()。為什么呢?因?yàn)橹苯訌?qiáng)引用的話,會在解析Test.Class的常量池時,利用系統(tǒng)默認(rèn)的ClassLoader加載了ClassTest,從而又觸發(fā)了ClassTest.Class的解析。為了避免這種情況的發(fā)生,這里利用CustomClassLoader去加載ClassTest.Class,再利用反射機(jī)制調(diào)用test(),此時在解析ClassTest.Class的常量池時,就會利用CustomClassLoader去加載Class常量池項(xiàng),也就不會發(fā)生異常了。

          四:總結(jié)

          寫完這篇文章,手也不癢了,甚爽!這篇文章從雙親委派講到了Class文件的加載,最后又繞回到雙親委派,看似有點(diǎn)繞,其實(shí)只有理解了Class的加載機(jī)制,才能更好的理解類似雙親委派這樣的機(jī)制,否則只死記硬背一些空洞的理論,是無法起到由內(nèi)而外的理解的。

          推薦閱讀:

          【107期】談?wù)劽嬖嚤貑柕腏ava內(nèi)存區(qū)域(運(yùn)行時數(shù)據(jù)區(qū)域)和內(nèi)存模型(JMM)

          【106期】面試官:Java中的finally一定會被執(zhí)行嗎?

          【105期】面試官:注冊中心全部宕掉后,Dubbo服務(wù)還能進(jìn)行調(diào)用嗎?

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號內(nèi)回復(fù)「2048」,即可免費(fèi)獲?。?!

          微信掃描二維碼,關(guān)注我的公眾號

          朕已閱?

          瀏覽 59
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  自拍偷拍在线播放 | 天天操天天久久精品 | 中文色网| 无限旅游团by燕惊鸿 | 国产最新激情视频在线 |