(點選上方公眾號,可快速關註)
來源:笨狐狸,
blog.csdn.net/liweisnake/article/details/17842877
在開始Java的類載入旅程之前,可以先參考這裡瞭解一些類載入器在Tomcat中的應用。
http://blog.csdn.net/liweisnake/article/details/8470285
在最初執行java這個命令時,便會呼叫 ClassLoader 的 getSystemClassLoader 方法顯式或者隱式載入 main 方法所在的類及其所取用的類。getSystemClassLoader 會傳回 AppClassLoader,後者是 URLClassLoader 的一個子類。
先有雞還是先有蛋?
所以,最初的一個問題是:先有雞還是先有蛋?因為 ClassLoader 的整套體系是打包在 jre/lib/rt.jar 中的。只有 rt.jar 先被載入進來,才能夠載入別的類;但是 rt.jar 又是被誰載入的呢?自然就是大名鼎鼎的 BootstrapClassLoader。它就是“雞”。所以嚴格來講,BootStrapClassLoader 並不是整個體系中的一部分(可以用 -Xbootclasspath 指定bootstrap 載入的位置)。
當 rt.jar 被載入進來後,ClassLoader 會呼叫 getSystemClassLoader,其中最重要的一步就是初始化 Launcher、ExtClassLoader 以及AppClassLoader,另外就是將 ContextClassLoader 設為 AppClassLoader。ExtClassLoader 與 AppClassLoader 都是 URLClassLoader 的子類,分別會載入 java.ext.dirs 和 java.class.path 路徑下的 jar資源,前者一般指向 jre/lib/ext 下的所有jar,後者就是我們經常唸叨的classpath。區分這兩個 ClassLoader 的主要目的是,讓他們形成層級關係,ExtClassLoader 為 AppClassLoader 的父 ClassLoader,有了層級關係,便可隨意使用雙親委託模型了。
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
“Could not create extension class loader”);
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
“Could not create application class loader”);
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
ClassLoader究竟幹了什麼?
接下來一個比較重要的問題是ClassLoader究竟幹了什麼?通常我們只知道它載入了一個類進了jvm,但是具體做了什麼呢?
Java設計者把classloader載入一個類的過程分為4步:
-
第一步,從某個地方得到我們想要的位元組碼二進位制流;
-
第二步,讀入位元組碼流並轉化為Class;
-
第三步,連結;
-
第四步,初始化。
其中,第二步一般比較固定,因此ClassLoader提供了defineClass來完成這步;
protected final Class > defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
Class c = null;
String source = defineClassSourceLocation(protectionDomain);
try {
c = defineClass1(name, b, off, len, protectionDomain, source);
} catch (ClassFormatError cfe) {
c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
source);
}
postDefineClass(c, protectionDomain);
return c;
}
而 ClassLoader 提供了另一個方法 findClass 來完成第一步及第二步,即從某個地方讀入類的二進位制流,然後呼叫 defineClass 傳回 Class
protected Class > findClass(final String name)
throws ClassNotFoundException
ClassLoader提供了resolveClass方法完成第三步連結的工作。
protected final void resolveClass(Class > c)
除非特殊需要,否則儘量多載 findClass 而不是 loadClass。
loadClass 是 Java 1.0 就存在的類,為了增強可擴充套件性,將findClass和resolveClass封裝到了loadClass中,一般我們只需要定義類的載入路徑,因此僅需改寫findClass。
通常我們顯示載入類一般會用到ClassLoader.loadClass、Class.forName,他們的區別見這裡。
http://blog.csdn.net/liweisnake/article/details/8857744
resolveClass做了什麼?
resolveClass最終呼叫了一個本地方法做link,這裡的link主要做了這麼幾步事情:
驗證Class以確保類裝載器格式和行為正確;
準備後續步驟所需的資料結構;
解析所取用的其他類。
關於這些內容的具體細節,請參考這裡。
http://blog.csdn.net/ns_code/article/details/17881581
ClassNotFoundException、NoClassDefFoundError、ClassCastException常見問題
ClassNotFoundException一般發生在顯式類載入;NoClassDefFoundError一般發生在隱式載入;ClassCastException一般發生在jar包衝突,比如某個jar包已經被更上層的載入器載入了,但你卻要求他強制轉為下層載入器載入的同名類;
連結的相關知識
接下來講講連結的相關知識。
為什麼會發明聯結器?
最初程式員寫程式都在一個檔案裡,隨著程式規模的增加,逐漸發現越來越難以維護,擴充套件。於是,分多個檔案和模組就成為必然。
但是檔案間必然相互呼叫,這就帶來了另一個問題:檔案A取用了B的某個變數或方法,但是執行時A並不知道他們在記憶體中的位置。於是人們發明瞭連結,這種方式會在編譯階段將需要取用的變數或者方法作個記號,這時形成A.o和B.o兩個標的檔案;透過ld聯結器的連結,會將B中變數或方法的地址重新修改A的記號最終形成一個可執行檔案,實際上就是把A和B合在一起工作了。
這種方式就是靜態連結。
但是靜態連結有個問題,比如A需要B模組的方法,C需要B模組的變數,D需要B模組的方法……如此一來,當我們編譯為A, C, D幾個可執行檔案時,他們都會在記憶體中取用B,即B在記憶體中有多份複製。這帶來很多問題:首先浪費了記憶體;其次修改了B模組需要重新修改併發布A, C, D幾個可執行檔案,這是非常不方便的;於是動態連結的一個思想是在A, C, D呼叫時再確定記號的地址,而B則透過延遲載入的方式按需載入到記憶體(如果已經載入則不再重覆)。這樣一來,記憶體中總是隻有一份B的複製,解決了上面的問題。Java的類載入機制即是這種靈活的方式。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能