java的類加載機制java的類加載器ClassLoader是一個抽象類,主要是用于将.class檔案加載到JVM記憶體中,并轉換成JVM可以識别的Class對象。ClassLoader類結構分析: 經常會用或擴充ClassLoader的幾個方法及其重載方法有: 1). Class> defineClass(byte[], int, int) 用來将byte位元組流解析成JVM能夠識别的Class對象,一般和findClass方法一起使用,通過直接覆寫ClassLoader父類的findClass方法來實作類的加載規則,進而取得加載類的位元組碼,再通過defineClass方法擷取類的Class對象.在運作時能加載自己指定的類,可以用下面的方法擷取: this.getClass().getClassLoader().loadClass("com.ran.test2.Test"); com.ran.test2.Test為含包名的類名 2). Class> findClass(String) 實作類的加載規則,進而取得加載類的位元組碼 3). Class> loadClass(String) 擷取指定類的Class對象 4). void resolveClass(Class>) 在對象真正執行個體化時才調用
ClassLoader的等級加載機制:加載機制:雙親委派機制雙親委派機制的過程是:一個類加載器收到類加載的請求時,會先請示父類加載器進行加載,如果父類加載器找不到(在其搜尋範圍内沒有找到),那麼才會由該類加載器自己進行加載。
從java虛拟機角度講,隻存在兩種不同的類加載器:啟動類加載器(BootStrap ClassLoader):使用C++實作,是虛拟機自身一部分;其他類加載器:使用Java實作,獨立于虛拟機外部,都繼承抽象類ClassLoader;
從Java開發角度講,可分為下面三種不同的加載器: 啟動類加載器(BootStrap ClassLoader): 主要加載JVM自身需要的類(JavaHome的lib目錄中且是虛拟機識别的類庫,如rt.jar),隻是一個類加載工具,沒有父加載器和子加載器; 擴充類加載器(ExtClassLoader):主要負責加載JavaHome的lib/ext目錄中和java.ext.dirs系統變量指定路徑中的類庫; 應用程式類加載器(AppClassLoader):主要負責加載使用者路徑上(ClassPath)所指定的類庫;
JVM加載class檔案到記憶體有兩種方式:1).隐式加載:不通過在代碼中調用ClassLoader來加載需要的類,而是通過JVM來自動加載需要的類到記憶體。2).顯示加載:在代碼中通過調用ClassLoader來加載一個類的方式,如: 如:this.getClass().getClassLoader().loadClass("com.ran.test2.Test"),Class.forName("com.ran.test2.Test")或者自己實作ClassLoader的findClass()方法
Class檔案的加載過程: 類加載的全過程包括五個階段:加載、驗證、準備、解析、初始化,其中驗證、準備、解析三個過程稱為連接配接。
加載:找到.class檔案并把檔案包含的位元組碼加載到記憶體中驗證:確定class檔案的位元組流中包含的資訊符合虛拟機的要求,主要包含四個檢驗過程:檔案格式嚴驗證、中繼資料驗證、位元組碼驗證、符号引用驗證。準備:為類變量(static修飾的變量)配置設定記憶體并設定初始值(此時雖然有初始值,但不執行java代碼,不進行初始化),預設基本類型初始值為0,引用類型為null。解析:将常量池中的符号引用替換為直接引用,主要針對類或接口、字段、類方法、接口方法四類的符号引用的解析。初始化:類第一次被使用時,才會初始化;主要是執行類構造器()方法,執行類變量的指派操作和靜态代碼塊。初始化發生的時機: new關鍵字執行個體化對象; 調用類的靜态方法: 給類的靜态域指派; 使用反射調用時,如果類沒初始化,需要先觸發其初始化; 初始化一個類時,如果父類沒有初始化,則需先觸發其父類的初始化; 虛拟機啟動時,使用者指定執行的主類,虛拟機需要初始化這個主類。
破壞雙親委派:第一次破壞:發生在雙親委派模型出現之前--即JDK1.2之前。JDK1.2之前,使用者自定義類加載器是繼承ClassLoader重寫loadClass()方法,因為虛拟機在進行類加載時會調用加載器的私有方法loadClassInternal(),而這個方法的唯一邏輯是調用自己的loadClass()方法。JDK1.2之後,不提倡使用者再去覆寫loadClass()方法,而是把自己的類加載邏輯寫到findClass()邏輯中。第二次破壞:模型自身缺陷導緻。JNDI服務采用線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器采用子類加載器去完成類加載的動作,java中所有涉及SPI的加載動作基本都是采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。第三次破壞:使用者對程式動态性的追求而導緻的。在OSGi環境下,類加載器不再是雙親委派中的樹狀結構,而是進一步發展為網狀結構。
自定義ClassLoader:
packagecom.ran.test02;public classTest01{
publicTest01() {
System.out.println("This is Test01");}
}packagecom.ran.test02;importjava.io.*;public classPathClassLoaderextendsClassLoader{
privateStringclassPath;privateStringpackageName= "com.ran.classpath";publicPathClassLoader(StringclassPath){
this.classPath= classPath;}
protectedClass> findClass(Stringname) throwsClassNotFoundException{
if(name.startsWith(packageName)){
byte[] classData = getData(name);if(classData == null){
throw newClassNotFoundException();} else{
returndefineClass(name,classData,0,classData.length);}
} else{
return super.loadClass(name);}
}
private byte[] getData(StringclassName) {
Stringpath = classPath+ File.separatorChar+ className.replace('.',File.separatorChar) + ".class";try{
InputStreamis = newFileInputStream(path);ByteArrayOutputStreambaos = newByteArrayOutputStream();byte[] buffer = new byte[2048];intnum = 0;while((num = is.read(buffer)) != -1){
baos.write(buffer,0,num);}
returnbaos.toByteArray();} catch(IOExceptione){
e.printStackTrace();}
return null;}
public static voidmain(String[] args) throwsException{
Stringpath = ClassLoader.getSystemResource("").getPath().substring(1);PathClassLoaderloader = newPathClassLoader(path);System.out.println(loader.findClass("com.ran.test02.Test01").newInstance());}
}
本文參考《深入了解虛拟機》
更多相關知識,可參考http://www.hollischuang.com/archives/201
http://www.hollischuang.com/archives/199