Java虛擬機類裝載的原理及實現
Java虛擬機(JVM)的類裝載就是指將包含在類文件中的字節碼裝載到JVM中, 並使其成爲JVM一部分的過程。JVM的類動態裝載技術能夠在運行時刻動態地加載或者替換系統的某些功能模塊, 而不影響系統其他功能模塊的正常運行。
在Java中,類裝載器把一個類裝入Java虛擬機中,要經過三個步驟來完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:
鏈接:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;
校驗:檢查導入類或接口的二進制數據的正確性;
準備:給類的靜態變量分配並初始化存儲空間;
解析:將符號引用轉成直接引用;
初始化:激活類的靜態變量的初始化Java代碼和靜態Java代碼塊。
至於在類裝載和虛擬機啓動的過程中的具體細節和可能會拋出的錯誤,請參看《Java虛擬機規範》以及《深入Java虛擬機》。 由於本文的討論重點不在此就不再多敘述。
③findSystemClass方法 findSystemClass方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用defineClass將字節數組轉換成Class對象,以將該文件轉換成類。當運行Java應用程序時,這是JVM 正常裝入類的缺省機制。
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; } ... } |
根(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中。