當前位置:才華齋>IT認證>JAVA認證>

2016最新java classloader詳解

JAVA認證 閱讀(3.18W)

Classloader 類載入器,用來載入Java類到 Java 虛擬機器中的一種載入器。那麼Classloader 類有什麼原理呢?下面跟yjbys小編一起來學習一下!

2016最新java classloader詳解

JAVA啟動後,是經過JVM各級ClassLoader來載入各個類到記憶體。為了更加了解載入過程,我通過分析和寫了一個簡單的ClassLoader來粗淺的分析它的原理。

JVM的ClassLoader分三層,分別為Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他們不是類繼承的父子關係,是邏輯上的上下級關係。

Bootstrap ClassLoader是啟動類載入器,它是用C++編寫的,從%jre%/lib目錄中載入類,或者執行時用-Xbootclasspath指定目錄來載入。

Extension ClassLoader是擴充套件類載入器,從%jre%/lib/ext目錄載入類,或者執行時用制定目錄來載入。

System ClassLoader,系統類載入器,它會從系統環境變數配置的classpath來查詢路徑,環境變數裡的.表示當前目錄,是通過執行時-classpath或指定的目錄來載入類。

一般自定義的Class Loader可以從sLoader繼承,不同classloader載入相同的類,他們在記憶體也不是相等的,即它們不能互相轉換,會直接拋異常。sLoader的核心載入方法是loadClass方法,如:

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 = Class(name, false);

} else {

c = findBootstrapClass0(name);

}

} catch (ClassNotFoundException e) {

// If still not found, then invoke findClass in order

// to find the class.

c = findClass(name);

}

}

if (resolve) {

resolveClass(c);

}

return c;

}

通過上面載入過程,我們能知道JVM預設是雙親委託載入機制,即首先判斷快取是否有已載入的類,如果快取沒有,但存在父載入器,則讓父載入器載入,如果不存在父載入器,則讓Bootstrap ClassLoader去載入,如果父類載入失敗,則呼叫本地的findClass方法去載入。

可以通過下面三條語句,輸入現在載入的各個classloader的載入路徑:

tln(":" + roperty(""));

tln(":" + roperty(""));

tln(":" +roperty(""));

ClassLoader cl = entThread()ontextClassLoader();//ystemClassLoader()

tln("getContextClassLoader:" +ring());

tln("nt:" +arent()ring());

tln("nt2:" +arent()arent());

輸出結果為:

:C:Program FilesJavajre7lib;C:Program FilesJavajre7lib;C:Program FilesJavajre7lib;C:Program FilesJavajre7lib;C:Program FilesJavajre7lib;C:Program FilesJavajre7lib;C:Program FilesJavajre7classes

:C:Program FilesJavajre7libext;C:WindowsSunJavalibext

:E:MyProjectsworkspaceTestConsolebin

getContextClassLoader:cher$AppClassLoader@19dbc3b

nt:cher$ExtClassLoader@b103dd

nt2:null

從上面的執行結果可以看出邏輯上的層級繼承關係。雙親委託機制的作用是防止系統jar包被本地替換,因為查詢方法過程都是從最底層開始查詢。 因此,一般我們自定義的classloader都需要採用這種機制,我們只需要繼承sLoader實現findclass即可,如果需要更多控制,自定義的classloader就需要重寫loadClass方法了,比如tomcat的載入過程,這個比較複雜,可以通過其他文件資料檢視相關介紹。

各個ClassLoader載入相同的類後,他們是不互等的,這個當涉及多個ClassLoader,並且有通過當前執行緒上線文獲取ClassLoader後轉換特別需要注意,可以通過執行緒的setContextClassLoader設定一個ClassLoader執行緒上下文,然後再通過entThread()ontextClassLoader()獲取當前執行緒儲存的Classloader。但是自定義的類檔案,放到Bootstrap ClassLoader載入目錄,是不會被Bootstrap ClassLoader載入的,因為作為啟動類載入器,它不會載入自己不熟悉的jar包的,並且類檔案必須打包成jar包放到載入器載入的根目錄,才可能被擴充套件類載入器所載入。

下面我自定義一個簡單的classloader:

public class TestClassLoader extends ClassLoader {

//定義檔案所在目錄

private static final String DEAFAULTDIR="E:MyProjectsworkspaceTestConsolebin";

public Class findClass(String name) throws ClassNotFoundException {

byte[] b = null;

try {

b = loadClassData(GetClassName(name));

} catch (Exception e) {

tStackTrace();

}

return defineClass(name, b, 0, th);

}

@Override

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

if(tsWith("java.")){try {

return Class(name, false);

} catch (ClassNotFoundException e) {

tStackTrace();

}

}

byte[] b = null;

try {

b = loadClassData(GetClassName(name));

} catch (Exception e) {

tStackTrace();

}

return defineClass(name, b, 0, th);

}

private byte[] loadClassData(String filepath) throws Exception {

int n =0;

BufferedInputStream br = new BufferedInputStream(

new FileInputStream(

new File(filepath)));

ByteArrayOutputStream bos= new ByteArrayOutputStream();

while((n=())!=-1){

e(n);

}

e();

return teArray();

}

public static String GetClassName(String name){

return DEAFAULTDIR+ace('.','/')+"s";

}

}

這個自定義的ClassLoader重寫了loadclass方法,但不用預設的雙親委託,比如包下面的都無法解析,這裡我簡單的判斷如果是java.開始的包則用父類去解析,能簡單的滿足雙親委託機制,但是其他相關非系統類載入也沒有用父類載入了。

測試程式碼如:

TestClassLoader liuloader = new TestClassLoader();

Myrunner runner = new Myrunner();

ontextClassLoader(liuloader);

t();

Myrunner是我自定義繼承自Thread的執行緒,通過設定執行緒上下文的classloader後,執行緒內部測試程式碼如:

ClassLoader cl1 = entThread()ontextClassLoader();

tln(cl1);

它將會輸出:

ClassLoader@347cdb,說明已經為當前執行緒上下文設定了自定義的Classloader了,如果這個執行緒內部通過這個classloader載入一個類,再轉換成當前的類,如程式碼:

Class c = Class("Loader2"); TestLoader2 tloader = (TestLoader2)nstance();

則為拋sCastException異常: Loader2 cannot be cast to Loader2。

因為cl1當前是 TestClassLoader載入的,而這個TestLoader2的.類還是預設由AppClassLoader載入,因此它們不能隱式轉換,Classloader載入相同的類,記憶體認為它們是沒有關係的物件。

如果把我自定義的TestClassLoader裡的LoadClass方法去掉,則採用了雙親委託機制,這樣我們除了指定的類以外,其他都會優先用父類來載入。這樣可以解決剛才的sCastException異常問題,為載入的物件建立一個抽象父類,自定義的Classloader負責載入子類,父類統一交給AppClassLoader或父載入器來載入,這樣執行緒內部可以使用類試:

Class c = Class("Loader2");

BaseTest tloader = (BaseTest)nstance();

BaseTest是TestLoader2的父類,因為BaseTest都是AppClassLoader或父載入器載入的,因此可以達到成功隱式轉換的目的。

對於Tomcat等幾個處理的Classloader都是自定義並重寫了loadclass方法,內部會更復雜處理。