日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

你知道 Java 類是如何被加載的嗎?

 

前言


最近給一個(gè)非 JAVA 方向的朋友講了下雙親委派模型,朋友讓我寫篇文章深度研究下JVM 的 ClassLoader,我確實(shí)也好久沒寫 JVM 相關(guān)的文章了,有點(diǎn)手癢癢,涂了皮炎平也抑制不住的那種。

我在向朋友解釋的時(shí)候是這么說的:雙親委派模型中,ClassLoader 在加載類的時(shí)候,會(huì)先交由它的父 ClassLoader 加載,只有當(dāng)父 ClassLoader 加載失敗的情況下,才會(huì)嘗試自己去加載。這樣可以實(shí)現(xiàn)部分類的復(fù)用,又可以實(shí)現(xiàn)部分類的隔離,因?yàn)椴煌?ClassLoader 加載的類是互相隔離的。

不過貿(mào)然的向別人解釋雙親委派模型是不妥的,如果在不了解 JVM 的類加載機(jī)制的情況下,又如何能很好的理解“不同 ClassLoader 加載的類是互相隔離的”這句話呢?所以為了理解雙親委派,最好的方式,就是先了解下 ClassLoader 的加載流程。

Java 類是如何被加載的


2.1:何時(shí)加載類


我們首先要清楚的是,Java 類何時(shí)會(huì)被加載?《深入理解 Java 虛擬機(jī)》給出的答案是:

  • 遇到 new、getstatic、putstatic 等指令時(shí)。
  • 對(duì)類進(jìn)行反射調(diào)用的時(shí)候。
  • 初始化某個(gè)類的子類的時(shí)候。
  • 虛擬機(jī)啟動(dòng)時(shí)會(huì)先加載設(shè)置的程序主類。
  • 使用 JDK 1.7 的動(dòng)態(tài)語言支持的時(shí)候。

其實(shí)要我說,最通俗易懂的答案就是:當(dāng)運(yùn)行過程中需要這個(gè)類的時(shí)候。

那么我們不妨就從如何加載類開始說起。

2.2:怎么加載類


利用 ClassLoader 加載類很簡(jiǎn)單,直接調(diào)用 ClassLoder 的 loadClass()方法即可,我相信大家都會(huì),但是還是要舉個(gè)例子:

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” 這個(gè)類,是不是 so easy。但是 JDK 提供的 API 只是冰山一角,看似很簡(jiǎn)單的一個(gè)調(diào)用,其實(shí)隱藏了非常多的細(xì)節(jié),我這個(gè)人吧,最喜歡做的就是去揭開 API 的封裝,一探究竟。

2.3:JVM 是怎么加載類的


JVM 默認(rèn)用于加載用戶程序的 ClassLoader 為 AppClassLoader,不過無論是什么ClassLoader,它的根父類都是 java.lang.ClassLoader。在上面那個(gè)例子中,loadClass()方法最終會(huì)調(diào)用到 ClassLoader.definClass1()中,這是一個(gè) Native 方法。

static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
 ProtectionDomain pd, String source);

看到 Native 方法莫心慌,不要急,打開 OpenJDK 源碼,我等繼續(xù)走馬觀花便是!

definClass1()對(duì)應(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()加載類,跟著源碼往下走,會(huì)發(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。對(duì)于Klass,大家可能不太熟悉,但是在這里必須得了解下。說白了,它就是 JVM 用來定義一個(gè) Java Class 的數(shù)據(jù)結(jié)構(gòu)。不過 Klass 只是一個(gè)基類,Java Class 真正的數(shù)據(jù)結(jié)構(gòu)定義在 InstanceKlass 中。

class InstanceKlass: public Klass {
 
 protected:
 
 Annotations* _annotations;
 ......
 ConstantPool* _constants;
 ......
 Array<jushort>* _inner_classes;
 ......
 Array<Method*>* _methods;
 Array<Method*>* _default_methods;
 ......
 Array<u2>* _fields;
}

可見 InstanceKlass 中記錄了一個(gè) Java 類的所有屬性,包括注解、方法、字段、內(nèi)部類、常量池等信息。這些信息本來被記錄在 Class 文件中,所以說,InstanceKlass 就是一個(gè) 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);

如果允許并行加載,則不會(huì)對(duì) ClassLoader 進(jìn)行加鎖,只對(duì) SystemDictionary 加鎖。否則,便會(huì)利用 ObjectLocker 對(duì) ClassLoader 加鎖,保證同一個(gè) ClassLoader 在同一時(shí)刻只能加載一個(gè)類。ObjectLocker 會(huì)在其構(gòu)造函數(shù)中獲取鎖,并在析構(gòu)函數(shù)中釋放鎖。允許并行加載的好處便是精細(xì)化了鎖粒度,這樣可以在同一時(shí)刻加載多個(gè) 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 注冊(cè)生成的 Klass 。

SystemDictionary 是用來幫助保存 ClassLoader 加載過的類信息的。準(zhǔn)確點(diǎn)說,SystemDictionary 并不是一個(gè)容器,真正用來保存類信息的容器是 Dictionary,每個(gè)ClassLoaderData 中都保存著一個(gè)私有的 Dictionary,而 SystemDictionary 只是一個(gè)擁有很多靜態(tài)方法的工具類而已。

我們來看看注冊(cè)的代碼:

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);
}

如果允許并行加載,那么前面就不會(huì)對(duì) ClassLoader 加鎖,所以在同一時(shí)刻,可能對(duì)同一 Class 文件加載了多次。但是同一 Class 在同一 ClassLoader 中必須保持唯一性,所以這里會(huì)先利用 SystemDictionary 查詢 ClassLoader 是否已經(jīng)加載過相同 Class。

如果已經(jīng)加載過,那么就將當(dāng)前線程剛剛加載的 InstanceKlass 加入待回收列表,并將 InstanceKlass* k 重新指向利用 SystemDictionary 查詢到的 InstanceKlass。如果沒有查詢到,那么就將剛剛加載的 InstanceKlass 注冊(cè)到 ClassLoader 的 Dictionary 中。

雖然并行加載不會(huì)鎖住 ClassLoader ,但是會(huì)在注冊(cè) InstanceKlass 時(shí)對(duì) SystemDictionary 加鎖,所以不需要擔(dān)心 InstanceKlass 在注冊(cè)時(shí)的并發(fā)操作。如果禁止了并行加載,那么直接利用 SystemDictionary 將 InstanceKlass 注冊(cè)到 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);

這里首先計(jì)算了InstanceKlass在內(nèi)存中的大小,要知道,這個(gè)大小在Class 文件編譯后就被確定了。

然后便 new 了一個(gè)新的 InstanceKlass 對(duì)象。這里并不是簡(jiǎn)單的在堆上分配內(nèi)存,要注意的是 Klass 對(duì) 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 的時(shí)候調(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)存不夠時(shí)會(huì)內(nèi)存溢出。

分配完 InstanceKlass 內(nèi)存后,便要著手第二件事,分析 Class文件,填充 InstanceKlass 內(nèi)存區(qū)域。

ClassFileParser 在構(gòu)造的時(shí)候就會(huì)開始分析Class文件,所以fill_instance_klass()中只需要填充即可。填充結(jié)束后,還會(huì)調(diào)用 java_lang_Class::create_mirror()創(chuàng)建 InstanceKlass 在Java 層的 Class 對(duì)象。

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);
}

順便提一句,對(duì)于 Class 文件結(jié)構(gòu)不熟悉的同學(xué),可以看下我兩年前寫的一篇文章:《汪先生:JVM 之用 Java 解析 class 文件》。

到這兒,Class 文件已經(jīng)完成了華麗的轉(zhuǎn)身,由冷冰冰的二進(jìn)制文件,變成了內(nèi)存中充滿生命力的 InstanceKlass。

再談雙親委派


如果你耐心的看完了上面的源碼分析,你一定對(duì) “不同ClassLoader加載的類是互相隔離的” 這句話的理解又上了一個(gè)臺(tái)階。

我們總結(jié)下:每個(gè) ClassLoader 都有一個(gè) Dictionary 用來保存它所加載的InstanceKlass 信息。并且,每個(gè) ClassLoader 通過鎖,保證了對(duì)于同一個(gè)Class,它只會(huì)注冊(cè)一份 InstanceKlass 到自己的 Dictionary 。

正式由于上面這些原因,如果所有的 ClassLoader 都由自己去加載 Class 文件,就會(huì)導(dǎo)致對(duì)于同一個(gè) Class 文件,存在多份 InstanceKlass,所以即使是同一個(gè) Class文件,不同 InstanceKlasss 衍生出來的實(shí)例類型也是不一樣的。

舉個(gè)例子,我們自定義一個(gè) 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)行后便會(huì)拋出類型強(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 對(duì)象所屬的 InstanceKlass 是由 CustomClassLoader 加載生成的,而我們要強(qiáng)轉(zhuǎn)的類型 Student.Class 對(duì)應(yīng)的 InstanceKlass 是由系統(tǒng)默認(rèn)的 ClassLoader 生成的,所以本質(zhì)上它們就是兩個(gè)毫無關(guān)聯(lián)的 InstanceKlass,當(dāng)然不能強(qiáng)轉(zhuǎn)。

有同學(xué)問到:為什么“強(qiáng)轉(zhuǎn)的類型 Student.Class 對(duì)應(yīng)的 InstanceKlass 是由系統(tǒng)默認(rèn)的 ClassLoader 生成的”?

其實(shí)很簡(jiǎn)單,我們反編譯下字節(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."<init>":([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 這個(gè)類在常量池中的索引:

#12 = Class #52 // com/wangxiandeng/Student

下面我們可以看看 checkcast 在HotSpot中的實(shí)現(xiàn)。HotSpot 目前有三種字節(jié)碼執(zhí)行引擎,目前采用的是模板解釋器,可以看下我這篇文章:《汪先生:JVM 之模板解釋器》。

早期的 HotSpot 采用的是字節(jié)碼解釋器。模板解釋器對(duì)于指令的執(zhí)行都是用匯編寫的,而字節(jié)碼解釋器采用的 C++ 進(jìn)行的翻譯,為了看起來比較舒服,我們就不看匯編了,直接看字節(jié)碼解釋器就行了。如果你的匯編功底很好,當(dāng)然也可以直接看模板解釋器,我之前寫的文章《汪先生:JVM之創(chuàng)建對(duì)象源碼分析》這里就是分析模板解釋器對(duì)于 new 指令的實(shí)現(xiàn)。

廢話不多說,我們來看看字節(jié)碼解釋器對(duì)于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)行解析,即將常量池中的符號(hào)引用替換成直接引用,
 //此時(shí)就會(huì)觸發(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 對(duì)應(yīng)的 InstanceKlass
 Klass* klassOf = (Klass*) METHOD->constants()->resolved_klass_at(index);
 // 獲取要強(qiáng)轉(zhuǎn)的對(duì)象的實(shí)際類型,即我們自己手動(dòng)加載的Student.Class 對(duì)應(yīng)的 InstanceKlass
 Klass* objKlass = STACK_OBJECT(-1)->klass(); // ebx
 
 // 現(xiàn)在就比較簡(jiǎn)單了,直接看看上面的兩個(gè)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);
 }

通過對(duì)上面代碼的分析,我相信大家已經(jīng)理解了 “強(qiáng)轉(zhuǎn)的類型Student.Class 對(duì)應(yīng)的 InstanceKlass 是由系統(tǒng)默認(rèn)的 ClassLoader 生成的” 這句話了。

雙親委派的好處是盡量保證了同一個(gè) Class 文件只會(huì)生成一個(gè) InstanceKlass,但是某些情況,我們就不得不去打破雙親委派了,比如我們想實(shí)現(xiàn)Class隔離的時(shí)候。

回復(fù)下 XM 同學(xué)的問題:

// 如果常量池還沒有解析,先進(jìn)行解析,即將常量池中的符號(hào)引用替換成直接引用,

//此時(shí)就會(huì)觸發(fā) Student.Class 的加載
if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {
CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);
}

請(qǐng)問,為何這里會(huì)重新加載 Student.Class?jvm 是不是有自己的 class 加載鏈路,然后系統(tǒng)循著鏈路去查找 class 是否已經(jīng)被加載?那該怎么把自定義的CustomClassloader 加到這個(gè)查詢鏈路中去呢?

第一種方法:設(shè)置啟動(dòng)參數(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)引用的話,會(huì)在解析 Test.Class 的常量池時(shí),利用系統(tǒng)默認(rèn)的 ClassLoader 加載了 ClassTest,從而又觸發(fā)了 ClassTest.Class 的解析。為了避免這種情況的發(fā)生,這里利用 CustomClassLoader 去加載 ClassTest.Class,再利用反射機(jī)制調(diào)用 test(),此時(shí)在解析 ClassTest.Class 的常量池時(shí),就會(huì)利用 CustomClassLoader 去加載 Class 常量池項(xiàng),也就不會(huì)發(fā)生異常了。


總結(jié)

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

分享到:
標(biāo)簽:Java
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定