結合JVM原始碼淺談Java類載入器
一、前言
之前文章 Java 類載入器揭祕 從Java層面講解了Java類載入器的原理,這裡我們結合JVM原始碼在稍微深入講解下。
二、Java類載入器的委託機制
Java 類載入器使用的是委託機制,也就是一個類載入器在載入一個類時候會首先嚐試讓父類載入器來載入。那麼問題來了,為啥使用這種方式?
使用委託第一這樣可以避免重複載入,第二,考慮到安全因素,下面我們看下ClassLoader類的loadClass方法:
protected Class<?> loadClass(Stringname,boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先從jvm快取查詢該類
Class c = findLoadedClass(name); // (1)
if (c ==null) {
longt0 = System.nanoTime();
try { //然後委託給父類載入器進行載入
if (parent !=null) {
c = parent.loadClass(name,false ); (2)
} else { //如果父類載入器為null,則委託給BootStrap載入器載入
c = findBootstrapClassOrNull(name); (3)
}
} catch (ClassNotFoundExceptione) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c ==null) {
// 若仍然沒有找到則呼叫findclass查詢
// to find the class.
longt1 = System.nanoTime();
c = findClass(name); (4)
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 -t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c); //(5)
}
returnc;
}
}
程式碼(1)表示從 JVM 快取查詢該類,如果該類之前被載入過,則直接從 JVM 快取返回該類。
程式碼(2)表示如果 JVM 快取不存在該類,則看當前類載入器是否有父載入器,如果有的話則委託父類載入器進行載入,否者呼叫(3),委託 BootStrapClassloader 進行載入,如果還是沒有找到,則呼叫當前 Classloader 的 findclass 方法進行查詢。
程式碼(4)則是從本地classloader指定路徑進行查詢,其中findClass方法在路徑找到Class檔案會載入二進位制位元組碼到記憶體,然後後會呼叫native方法defineClass1解析位元組碼為JVM內部的kclass物件,然後存放到Java堆的方法區。
程式碼(5)則是當位元組碼載入到記憶體後進行連結操作,對檔案格式和位元組碼驗證,併為 static 欄位分配空間並初始化,符號引用轉為直接引用,訪問控制,方法覆蓋等,本文對這些不進入深入探討。
三、JVM原始碼之defineClass1如何解析位元組碼檔案
本節使用的openjdk7的原始碼,JVM原始碼中defineClass1的定義是在ClassLoader.c檔案,其解析時序圖如下:
image.png可知步驟(8)具體解析位元組碼檔案,步驟(17)新增載入的類到系統詞典Map裡面,
void SystemDictionary::update_dictionary(int d_index, unsigned int d_hash,
int p_index, unsigned int p_hash,
instanceKlassHandle k,
Handle class_loader,
TRAPS) {
// Compile_lock prevents systemDictionary updates during compilations
assert_locked_or_safepoint(Compile_lock);
Symbol* name = k->name();
ClassLoaderData *loader_data = class_loader_data(class_loader);
{
MutexLocker mu1(SystemDictionary_lock, THREAD);
...
// 當前物件已經存在了?
Klass* sd_check = find_class(d_index, d_hash, name, loader_data);
//不存在則新增
if (sd_check == NULL) {
//新增kclass到系統詞典
dictionary()->add_klass(name, loader_data, k);
notice_modification();
}
...
}
其中key使用類的包路徑+類名,類載入器兩者確定,value則為具體載入的類對應的instanceKlassHandle物件,其中維護這kclass物件。也就是系統詞典裡面使用類載入器和類的包路徑類名唯一確定一個類。這也驗證了在Java中同一個類使用兩個類載入器進行載入後,載入的兩個類是不一樣的,是不能相互賦值的。
四、JVM原始碼之findLoadedClass0如何查詢一個類是否被載入過了
findLoadedClass0也是在ClassLoader.c檔案裡面,其查詢時序圖:
image.png如上時序圖主要看 SystemDictionary的find方法:
Klass* SystemDictionary::find(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
...
class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));
ClassLoaderData* loader_data = ClassLoaderData::class_loader_data_or_null(class_loader());
...
unsigned int d_hash = dictionary()->compute_hash(class_name, loader_data);
int d_index = dictionary()->hash_to_index(d_hash);
{
...
return dictionary()->find(d_index, d_hash, class_name, loader_data,
protection_domain, THREAD);
}
}
Klass* Dictionary::find(int index, unsigned int hash, Symbol* name,
ClassLoaderData* loader_data, Handle protection_domain, TRAPS) {
//根據類名和載入器計算對應的kclass在map裡面對應的key
DictionaryEntry* entry = get_entry(index, hash, name, loader_data);
//存在,並且驗證通過則返回
if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) {
return entry->klass();
} else {
//否者返回null,說明不存在
return NULL;
}
}
可知在查詢一個類是否已經被載入過後,也是從系統詞典裡面根據類名和類載入器去查詢是否存在的。
五、總結
本文從JVM原始碼角度分析了Java中唯一含有包路徑的類名和類載入器唯一確定了一個類,在全域性系統詞典裡面就是根據包路徑的類名和類載入器計算載入的類對應的key的。