Java代碼首先要編譯成class文件字節碼,在運行時通過JIT(即時編譯器)編譯成本地機器碼,最後由ClassLoader將其加載解析成Class對象到內存中。通過ClassLoader的loadClass方法的源碼加深對Java類加載機制的理解。
1. ClassLoader加載機制簡述:
Java的類加載遵循父類優先的原則,也就是說ClassLoader是一個有層級的樹形組合體系,並且一個ClassLoader要加載一個類,首先逐層向上檢查是否有加載器加載過該類,如果有,將結果逐層返回到下級。如果沒有,繼續檢查直到有一層ClassLoader返回沒有加載並且它不應該加載,那麼該層的下一層就可以加載該類。
PS:父類優先的方式也不是萬能的,在JavaEE Web應用程序中,也會使用子女優先加載的方式;
1.1 JVM提供3層基本的類加載平臺:
BootstrapClassLoader:加載JVM自身需要的類,注意在Hotspot JVM中它嚴格來說不是JVM類加載體系中的,它並不遵循上述機制,也不是下面ExtClassLoader的父類加載器;
ExtClassLoader:加載特定的類:System.getProperty("java.ext.dirs");也就是JRE/LIB/EXT目錄下的類,加載的是sun公司的一些擴展包,它是AppClassLoader的父類加載器;
AppClassLoader:加載System.getProperty("java.class.path");就是classpath,看到這個你可能已經知道eclipse項目下.classpath的作用了,就是告訴AppClassLoader這些類由它加載;
繼承自URLClassLoader的自定義類加載器,通過調用getSystemClassLoader獲取自己的父加載器(AppClassLoader);
ClassLoader的類層次結構:
圖中的AppClassLoader和ExtClassLoader是Launcher的內部類;
到現在,我們可以也可以看出Java的ClassLoader使用了職責鏈設計模式,父優先加載,一定程度上保證了程序安全(防止惡意代碼替換JSE核心類)。
1.2 JVM加載Class文件到內存的方式:
一是隱式加載:繼承或引用某個類時,有JVM負責加載;
二是顯式加載:在代碼中調用loadClass(),Class.forName,ClassLoader的findClass方法等,顯式加載中也可能包含隱式加載;
2. ClassLoader的重要方法:
findClass:主要由URLClassLoader實現,根據URLClassPath去指定地方查找class文件;取得要加載class文件的字節流;
defineCLass:可以將字節流解析成Class對象,該Class對象並未進行resolve;
resolveClass:對Class對象進行Link,載入引用類(超類,接口字段,方法簽名,方法中的本地變量);
loadClass:採用默認的加載邏輯根據類名加載一個類,返回Class對象,調用前面3個方法實現;
3. 加載class文件的過程:
3.1 加載字節碼到內存:findClass和defineClass方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
//獲取特權,確保有權限可以讀取到資源,這裏
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//將完整的類名轉換成文件路徑格式
String path = name.replace('.', '/').concat(".class");
//在指定的URLPath中獲取對應的class文件資源
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//獲取成功將資源傳入,最終獲取未解析的Class對象
return defineClass(name, res);
} catch (IOException e) {
//不能成功讀取文件內容
throw new ClassNotFoundException(name, e);
}
} else {
//不能獲取資源
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
//因爲要在特權操作中拋出ClassNotFoundException,使用了PrivilegedExceptionAction回調
//它會將異常包裝,這裏要解除包裝
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
//首先加載包
if (i != -1) {
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
//使用nio,獲取字節緩衝區
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
//獲取不到緩衝區,直接InputStream獲取字節數組
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs);
}
}
在這個方法中,我們可以看到對於class文件的讀取策略,其中CodeSigner和CodeSource分別是代碼簽名和代碼源,它們組合使用與前面提及的保護域機制(ProtectionDomain)當中,可見Java中類加載機制和安全模型是密不可分的。
3.2 驗證和解析:defineClass方法和resovleClass方法:
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
//首先檢查類名;阻止加載“java.”開頭包內的類(應有BootStrapLoader加載);
//確保同一個包內的Class擁有相同的證書
protectionDomain = preDefineClass(name, protectionDomain);
//根據CodeSource獲取一個URL的字符串表示
String source = defineClassSourceLocation(protectionDomain);
//字節碼驗證;類準備(準備字段,方法,實現接口所必需的數據結構);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
//通過證書爲該類設置簽名
postDefineClass(c, protectionDomain);
return c;
}
(3)解析:resolveClass方法,直接通過一個native方法實現
3.3 顯式加載時的過程:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//該getClassLoadingLock獲取同步鎖,該鎖用併發Map保存(享元模式)
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 這裏體現了類加載機制中的父優先的查找機制
// 通過上濾直到parent爲null
// 這時再去BootstrapClassLoader中查找,在運行用戶程序時,這一步一般都是null
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//載入字節碼到內存
c = findClass(name);
// 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);
}
return c;
}
}