本章內容是對《深入理解Java虛擬機器:JVM高階特性和最佳實踐》的理解和概括。
前言
在上文中我們已經講了類的載入機制,這一章的主角就是類載入器和雙親委派模型了。
類載入器
在Java虛擬機器中,類載入器十分重要。每一個類的載入,都需要通過一個類的載入器。但是如果我們建立一個屬於自己的類載入器,這個時候會出現一個什麼樣的情況呢? 接下來,我們用程式碼來進行驗證測試。
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
// 由自己的類載入器建立物件
Object obj = myLoader.loadClass("XuNiJi.ClassLoaderTest").newInstance();
// 由系統提供的類載入器載入建立物件
Object obj1 = ClassLoader.getSystemClassLoader().loadClass("XuNiJi.ClassLoaderTest").newInstance();
System.out.println(obj instanceof ClassLoaderTest);
System.out.println(obj1 instanceof ClassLoaderTest);
}
}
複製程式碼
從這裡想來已經能夠看出了,由一個類載入器統一建立的類,才存在可比性。因為類載入器是擁有獨立的類名稱空間的。更簡單的說,就像上面的例子,如果不使用Java虛擬機器提供的類載入器,你就會失去一大部分功能,比如equals()
、isAssignableFrom()
、isInstance()
、instanceof
。如果要相同,除非你直接在java原始碼上動手腳。
雙親委派模型
第一個問題:為什麼需要這個模型?
其實這個模型的提出,就是為了解決類載入器可能不出現不同的問題。因為即便是相同的class
,由不同的類載入器載入時,結果就是不同的。
工作原理
雙親委派的工作流程非常簡單,這就跟之前文章裡的Android的事件分發機制一樣,向上傳遞,由上一層的載入器先行嘗試消費,如果上一層無法完成這個任務,那麼子載入器就要由自己動手完成。
- 啟動類載入器:負責載入/lib下的類。
- 擴充套件類載入器:負責載入/lib/ext下的類。
- 系統類載入器/應用程式類載入器:
ClassLoader.getSystemClassLoader
返回的就是它。
通過上圖我們可以知道,子載入器不斷的給上一層載入器傳遞載入請求,那麼這個時候啟動類載入器勢必是接受到過全部的載入請求的。如果不信,我們就用原始碼來證明。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 判斷Class是否被載入過
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父類丟擲ClassNotFoundException
// 說明父類無法完成載入
}
// 這個時候c依舊為null,說明父類載入不了
// 那沒有辦法,只能子載入器自己效勞了
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
複製程式碼
講完了他的工作原理,自然就要知道,他能夠如何被破壞的了。
破壞雙親委派模型
第二個問題,為什麼要破壞雙親委派?
拿最簡單的例子,在上文中我們,提到過各個資源的載入範圍,但是Driver
作為後來才加入的一個介面,他的很多api是由第三方服務商開發的。那麼這個時候,破壞雙親委派就有了他的用武之地了,當然這只是他的用處之一。
下面來介紹,他是如何破壞雙親委派的。 先看看我們平時都是怎麼用的。(當然這是很基礎的寫法了,因為現在池的概念加深,所以很多事情都已經被封裝了。)
String url = "jdbc:mysql://localhost:3306/db";
Connection conn = DriverManager.getConnection(url, "root", "root");
複製程式碼
上面很明顯就能看出這件事情就是關於DriverManager
展開的了。
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
複製程式碼
這裡根據前一章的內容先要對DriverManager
進行初始化,也就是呼叫了一個loadInitialDrivers()
函式。
private static void loadInitialDrivers() {
.....
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);// 1
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
.....
}
複製程式碼
從這一小段中,我們關注註釋1
能夠知道他專門去訪問了一個ServiceLoader
的類,點進去之後我們能夠發現這麼三段程式碼。
// 1
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 2
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
// 3
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
複製程式碼
由1 --> 2 --> 3的順序循序漸進,你是否已經和我關注到一個問題了!!
ClassLoader.getSystemClassLoader()
,看到這個函式了嗎,我在上文提到過,這個函式我們獲得的類載入器將會是應用程式類載入器。也就是說我們的任務不會再向上傳遞了,到頭就是到了應用程式類載入器這個位置,那麼雙親委派模型也就破壞了。
以上就是打破雙親委派的方法之一的介紹了。
溯源ClassLoader.getSystemClassLoader()
為什麼說我們呼叫的是應用程式類載入器呢?
接下來直接從原始碼來解析了。
首先就是呼叫getSystemClassLoader()
這個函式了
這張圖裡我們只用關注圈紅的函式。
然後在initSystemClassLoader()
函式中呼叫了一個Launcher
的類。
而Launcher
整個類的建立,想來讀者也已經看到loader
這個變數了,通過getAppClassLoader()
這個函式所建立的loader
也就是我們口中所說的應用程式類載入器了。
以上就是我的學習成果,如果有什麼我沒有思考到的地方或是文章記憶體在錯誤,歡迎與我分享。
相關文章推薦: