Java類載入器深入理解

ericquan8發表於2016-01-13

本篇文章主要是詳細寫一下個人對Java ClassLoader的理解。

首先回顧一下,java虛擬機器載入java類的步驟:java檔案經過編譯器編譯後變成位元組碼檔案(.class檔案),類載入器(ClassLoader)讀取.class檔案,並且轉換成java.lang.Class的一個例項,最後通過newInstance方法建立該類的一個物件。ClassLoader的作用就是根據一個類名,找到對應的位元組碼,根據這些位元組碼定義出對應的類,該類就是java.lang.Class的一個例項。

類載入器的組織結構

java有三個初始類載入器,當java虛擬機器啟動時,它們會按照以下順序啟動:Bootstrap classloader -> extension classloader -> system classloader。三者的關係:bootstrap classloader是extension classloader的parent,extension classloader是system classloader的parent。

bootstrap classloader

它是最原始的類載入器,並不是由java程式碼寫的,是由原生程式碼編寫的。Java有一次編譯、所有平臺執行的效果,就是因為它寫了一份功能相同,但針對不同平臺不同語言實現的底層程式碼。它負責載入java核心庫,大家可執行以下程式碼,看看自己本地的java核心庫在哪裡:

URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
	System.out.println(urls[i].toExternalForm());
}

本人的執行結果:

file:/home/eric/jdk1.6.0_35/jre/lib/resources.jar
file:/home/eric/jdk1.6.0_35/jre/lib/rt.jar
file:/home/eric/jdk1.6.0_35/jre/lib/sunrsasign.jar
file:/home/eric/jdk1.6.0_35/jre/lib/jsse.jar
file:/home/eric/jdk1.6.0_35/jre/lib/jce.jar
file:/home/eric/jdk1.6.0_35/jre/lib/charsets.jar
file:/home/eric/jdk1.6.0_35/jre/lib/modules/jdk.boot.jar
file:/home/eric/jdk1.6.0_35/jre/classes

extension classloader

它用來載入JRE的擴充套件目錄(JAVA_HOME/jre/lib/ext或java.ext.dirs系統屬性指定的)JAR的類包。注意,因為它是bootstrap classloader載入的,所以當你執行:

ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());

輸出的是:the parent of extension classloader : null

system classloader

它用於載入classpath目錄下的jar包,我們寫的java類,一般都是由它載入,除非你自己制定個人的類載入器。

全盤負責委託機制

classloader載入類時,使用全盤負責委託機制,可以分開兩部分理解:全盤負責,委託。

全盤負責機制:若類A呼叫了類B,則類B和類B所引入的所有jar包,都由類A的類載入器統一載入。

委託機制:類載入器在載入類A時,會優先讓父載入器載入,當父載入器載入不到,再找父父載入器,一直找到bootstrap  classloader都找不到,才自己去相關的路徑去尋找載入。以下是ClassLoader的原始碼:

protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	Class c = findLoadedClass(name);
	if (c == null) {
	    try {
		if (parent != null) {
		    //從父載入器載入
		    c = parent.loadClass(name, false);
		} else {
		    //從bootstrap loader載入
		    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.
	        c = findClass(name);
	    }
	}
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }

舉個例子,類載入器載入類A的過程:

1,判斷是否已經載入過,在cache裡面查詢,若有,跳7;否則下一步

2,判斷當前載入器是否有父載入器,若無,則當前為ext classloader,跳去4;否則下一步

3,請求父載入器載入該類,若載入成功,跳7;若不成功,即父載入器不能找到該類,跳2

4,請求jvm的bootstrap classloader載入,若載入成功,跳7;若失敗,跳5

5,當前載入器自己載入,若成功,跳7;否則,跳6

6,丟擲ClassNotFoundException

7,返回Class

編寫自己的類載入器

Java載入類的過程,實質上是呼叫loadClass()方法,loadClass中呼叫findLoadedClass()方法來檢查該類是否已經被載入過,如果沒有就會呼叫父載入器的loadClass(),如果父載入器無法載入該類,就呼叫findClass()來查詢該類。

所以我們要做的就是新建MyClassLoader繼承java.lang.ClassLoader,重寫其中的findClass()方法。主要是重新設計查詢位元組碼檔案的方案,然後呼叫definedClass來返回。

本人寫了一個demo,用自己的類載入器去載入指定java檔案,且帶有熱部署效果,具體請檢視以下url。

Demo地址:http://git.oschina.net/ericquan8/hot-deploy

相關文章