1、引言html
Java虛擬機(JVM)的類裝載就是指將包含在類文件中的字節碼裝載到JVM中, 並使其成爲JVM一部分的過程。JVM的類動態裝載技術可以在運行時刻動態地加載或者替換系統的某些功能模塊, 而不影響系統其餘功能模塊的正常運行。本文將分析JVM中的類裝載系統,探討JVM中類裝載的原理、實現以及應用。java
2、Java虛擬機的類裝載實現與應用bootstrap
2.1 裝載過程簡介數組
所謂裝載就是尋找一個類或是一個接口的二進制形式並用該二進制形式來構造表明這個類或是這個接口的class對象的過程,其中類或接口的名稱是給定了的。固然名稱也能夠經過計算獲得,可是更常見的是經過搜索源代碼通過編譯器編譯後所獲得的二進制形式來構造。瀏覽器
在Java中,類裝載器把一個類裝入Java虛擬機中,要通過三個步驟來完成:裝載、連接和初始化,其中連接又能夠分紅校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工做以下:安全
裝載:查找和導入類或接口的二進制數據;
連接:執行下面的校驗、準備和解析步驟,其中解析步驟是能夠選擇的;
校驗:檢查導入類或接口的二進制數據的正確性;
準備:給類的靜態變量分配並初始化存儲空間;
解析:將符號引用轉成直接引用;
初始化:激活類的靜態變量的初始化Java代碼和靜態Java代碼塊。網絡
至於在類裝載和虛擬機啓動的過程當中的具體細節和可能會拋出的錯誤,請參看《Java虛擬機規範》以及《深刻Java虛擬機》,它們在網絡上面的資源地址是:
http://java.sun.com/docs/books/vmspec/2nd-edition/html/Preface.doc.html
http://www.artima.com/insidejvm/ed2/index.html
因爲本文的討論重點不在此就再也不多敘述。數據結構
2.2 裝載的實現app
JVM中類的裝載是由ClassLoader和它的子類來實現的,Java ClassLoader 是一個重要的Java運行時系統組件。它負責在運行時查找和裝入類文件的類。jvm
在Java中,ClassLoader是一個抽象類,它在包java.lang中,能夠這樣說,只要瞭解了在ClassLoader中的一些重要的 方法,再結合上面所介紹的JVM中類裝載的具體的過程,對動態裝載類這項技術就有了一個比較大概的掌握,這些重要的方法包括如下幾個:
①loadCass方法 loadClass(String name ,boolean resolve)其中name參數指定了JVM須要的類的名稱,該名稱以包表示法表示,如Java.lang.Object;resolve參數告訴方法 是否須要解析類,在初始化類以前,應考慮類解析,並非全部的類都須要解析,若是JVM只須要知道該類是否存在或找出該類的超類,那麼就不須要解析。這個 方法是ClassLoader 的入口點。
②defineClass方法 這個方法接受類文件的字節數組並把它轉換成Class對象。字節數組能夠是從本地文件系統或網絡裝入的數據。它把字節碼分析成運行時數據結構、校驗有效性等等。
③findSystemClass方法 findSystemClass方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,若是存在,就使用defineClass將字節數組轉換成 Class對象,以將該文件轉換成類。當運行Java應用程序時,這是JVM 正常裝入類的缺省機制。
④resolveClass方法 resolveClass(Class c)方法解析裝入的類,若是該類已經被解析過那麼將不作處理。當調用loadClass方法時,經過它的resolve 參數決定是否要進行解析。
⑤findLoadedClass方法 當調用loadClass方法裝入類時,調用findLoadedClass 方法來查看ClassLoader是否已裝入這個類,若是已裝入,那麼返回Class對象,不然返回NULL。若是強行裝載已存在的類,將會拋出連接錯 誤。
2.3 裝載的應用
通常來講,咱們使用虛擬機的類裝載時須要繼承抽象類java.lang.ClassLoader,其中必須實現的方法是loadClass(),對 於這個方法須要實現以下操做:(1) 確認類的名稱;(2) 檢查請求要裝載的類是否已經被裝載;(3) 檢查請求加載的類是不是系統類;(4) 嘗試從類裝載器的存儲區獲取所請求的類;(5) 在虛擬機中定義所請求的類;(6) 解析所請求的類;(7) 返回所請求的類。
全部的Java 虛擬機都包括一個內置的類裝載器,這個內置的類庫裝載器被稱爲根裝載器(bootstrap ClassLoader)。根裝載器的特殊之處是它只可以裝載在設計時刻已知的類,所以虛擬機假定由根裝載器所裝載的類都是安全的、可信任的,能夠不通過 安全認證而直接運行。當應用程序須要加載並非設計時就知道的類時,必須使用用戶自定義的裝載器(user-defined ClassLoader)。下面咱們舉例說明它的應用。
public abstract class MultiClassLoader extends ClassLoader{
...
public synchronized Class loadClass(String s, boolean flag)
throws ClassNotFoundException
{
/* 檢查類s是否已經在本地內存*/
Class class1 = (Class)classes.get(s);
/* 類s已經在本地內存*/
if(class1 != null) return class1;
try/*用默認的ClassLoader 裝入類*/ {
class1 = super.findSystemClass(s);
return class1;
}
catch(ClassNotFoundException _ex) {
System.out.println(">> Not a system class.");
}
/* 取得類s的字節數組*/
byte abyte0[] = loadClassBytes(s);
if(abyte0 == null) throw new ClassNotFoundException();
/* 將類字節數組轉換爲類*/
class1 = defineClass(null, abyte0, 0, abyte0.length);
if(class1 == null) throw new ClassFormatError();
if(flag) resolveClass(class1); /*解析類*/
/* 將新加載的類放入本地內存*/
classes.put(s, class1);
System.out.println(">> Returning newly loaded class.");
/* 返回已裝載、解析的類*/
return class1;
}
...
}
3、Java虛擬機的類裝載原理
前面咱們已經知道,一個Java應用程序使用兩種類型的類裝載器:根裝載器(bootstrap)和用戶定義的裝載器(user- defined)。根裝載器是Java虛擬機實現的一部分,舉個例子來講,若是一個Java虛擬機是在如今已經存在而且正在被使用的操做系統的頂部用C程 序來實現的,那麼根裝載器將是那些C程序的一部分。根裝載器以某種默認的方式將類裝入,包括那些Java API的類。在運行期間一個Java程序能安裝用戶本身定義的類裝載器。根裝載器是虛擬機固有的一部分,而用戶定義的類裝載器則不是,它是用Java語言 寫的,被編譯成class文件以後而後再被裝入到虛擬機,並像其它的任何對象同樣能夠被實例化。 Java類裝載器的體系結構以下所示:
圖1 Java的類裝載的體系結構
Java的類裝載模型是一種代理(delegation)模型。當JVM 要求類裝載器CL(ClassLoader)裝載一個類時,CL首先將這個類裝載請求轉發給他的父裝載器。只有當父裝載器沒有裝載並沒有法裝載這個類 時,CL纔得到裝載這個類的機會。這樣, 全部類裝載器的代理關係構成了一種樹狀的關係。樹的根是類的根裝載器(bootstrap ClassLoader) , 在JVM 中它以"null"表示。除根裝載器之外的類裝載器有且僅有一個父裝載器。在建立一個裝載器時, 若是沒有顯式地給出父裝載器, 那麼JVM將默認系統裝載器爲其父裝載器。Java的基本類裝載器代理結構如圖2所示:
圖2 Java類裝載的代理結構
下面針對各類類裝載器分別進行詳細的說明。
根(Bootstrap) 裝載器:該裝載器沒有父裝載器,它是JVM實現的一部分,從sun.boot.class.path裝載運行時庫的核心代碼。
擴展(Extension) 裝載器:繼承的父裝載器爲根裝載器,不像根裝載器可能與運行時的操做系統有關,這個類裝載器是用純Java代碼實現的,它從java.ext.dirs (擴展目錄)中裝載代碼。
系統(System or Application) 裝載器:裝載器爲擴展裝載器,咱們都知道在安裝JDK的時候要設置環境變量(CLASSPATH ),這個類裝載器就是從java.class.path(CLASSPATH 環境變量)中裝載代碼的,它也是用純Java代碼實現的,同時仍是用戶自定義類裝載器的缺省父裝載器。
小應用程序(Applet) 裝載器: 裝載器爲系統裝載器,它從用戶指定的網絡上的特定目錄裝載小應用程序代碼。
在設計一個類裝載器的時候,應該知足如下兩個條件:
對於相同的類名,類裝載器所返回的對象應該是同一個類對象
若是類裝載器CL1將裝載類C的請求轉給類裝載器CL2,那麼對於如下的類或接口,CL1和CL2應該返回同一個類對象:a)S爲C的直接超 類;b)S爲C的直接超接口;c)S爲C的成員變量的類型;d)S爲C的成員方法或構建器的參數類型;e)S爲C的成員方法的返回類型。
每一個已經裝載到JVM中的類都隱式含有裝載它的類裝載器的信息。類方法getClassLoader 能夠獲得裝載這個類的類裝載器。一個類裝載器認識的類包括它的父裝載器認識的類和它本身裝載的類,可見類裝載器認識的類是它本身裝載的類的超集。注意咱們 能夠獲得類裝載器的有關的信息,可是已經裝載到JVM中的類是不能更改它的類裝載器的。
Java中的類的裝載過程也就是代理裝載的過程。好比:Web瀏覽器中的JVM須要裝載一個小應用程序TestApplet。JVM調用小應用程序 裝載器ACL(Applet ClassLoader)來完成裝載。ACL首先請求它的父裝載器, 即系統裝載器裝載TestApplet是否裝載了這個類, 因爲TestApplet不在系統裝載器的裝載路徑中, 因此係統裝載器沒有找到這個類, 也就沒有裝載成功。接着ACL本身裝載TestApplet。ACL經過網絡成功地找到了TestApplet.class 文件並將它導入到了JVM中。在裝載過程當中, JVM發現TestAppet是從超類java.applet.Applet繼承的。因此JVM再次調用ACL來裝載 java.applet.Applet類。ACL又再次按上面的順序裝載Applet類, 結果ACL發現他的父裝載器已經裝載了這個類, 因此ACL就直接將這個已經裝載的類返回給了JVM , 完成了Applet類的裝載。接下來,Applet類的超類也同樣處理。最後, TestApplet及全部有關的類都裝載到了JVM中。
4、結論
類的動態裝載機制是JVM的一項核心技術, 也是容易被忽視而引發不少誤解的地方。本文介紹了JVM中類裝載的原理、實現以及應用,尤爲分析了ClassLoader的結構、用途以及如何利用自定義 的ClassLoader裝載並執行Java類,但願能使讀者對JVM中的類裝載有一個比較深刻的理解。
原文連接http://developer.51cto.com/art/200508/1380.htm
另,JDBC的driver是程序在runtime裝載的,而非啓動時裝載的d