注:本文是個人對java虛擬機規(guī)范提到的知識的一點總結(jié),。
在Java中,類必須經(jīng)過jvm使用類裝載器(class loader)裝載(load)之后才能使用,。以主程序(Class A)為例,,當jvm調(diào)用程序的main方法時,在沒有裝載A.class文件時,,這是不可能的事情,。
裝載class文件是程序執(zhí)行的第一步,這跟linux下的程序執(zhí)行器(execve)裝載目標文件的原理是一樣的,,jvm充當execve的角色,,讀取 class文件的二進制數(shù)據(jù),轉(zhuǎn)換成jvm內(nèi)部能夠識別的數(shù)據(jù)結(jié)構(gòu),。在這個過程中生成了一個A類關(guān)聯(lián)的Class對象,,該Class對象記錄了A類相關(guān)的數(shù)據(jù)。
一個類真正能使用要經(jīng)過裝載,、連接,、初始化三個步驟。連接又可以分為“驗證”,、”準備“,、”解析 “三個步驟??傮w說來,,由于class文件本身記錄了很多數(shù)據(jù)結(jié)構(gòu)(常量池、字段信息,、方法信息,、引用信息等),必須要轉(zhuǎn)換成內(nèi)部形式,,這個過程就通過裝載來實現(xiàn),,但是,class文件自身并沒有完成鏈接,,這跟C的目標文件有很大差別——其實也就是解析執(zhí)行和編譯執(zhí)行的區(qū)別了,,裝載之后形成的內(nèi)部結(jié)構(gòu)還存在很多符號引用,,需要resolve引用,,這就是連接過程,原理跟C的鏈接是一樣——解決內(nèi)部和外部符號引用,。
在連接過程,,jvm試圖解析類A中出現(xiàn)的符號引用,,比如A中定義了:
private B b=new B();
符號引用b是一個字段引用,B()是一個方法引用,,并且B是定義在別的class文件的(一個類只能對應(yīng)一個class文件),,所以jvm試圖解析 b,這個過程同時要對B進行裝載(如果B并沒有被當前裝載器裝載),,裝載過程一般是遞歸進行的,,一直到達最高類層次(Object)。
關(guān)于JVM是如何裝載,、連接,、初始化的,內(nèi)容太多,,詳細的信息要參考Java虛擬機規(guī)范,。
Java中(jdk 1.6版)有三種加載器,啟動加載器→擴展加載器(ExtClassLoader)→用戶程序加載器(AppClassLoader),。箭頭左邊的類是右邊類的父類,。其中啟動類加載器對于我們是不可見的,用于加載java源碼包的類以及jdk安裝路徑下的類(rt.jar etc),,而擴展類加載器用于加載ext目錄下的類,,用戶類加載器則是加載普通的class文件。
Java給我們提供了使用自定義類裝載器來裝載程序的可能,,這有利于程序的擴展,。想想看applet 的運行,JDBC驅(qū)動的裝載(典型的JDBC訪問語句Class,forName()),,我們在編譯的時候能知道它們要裝載什么類型的類嗎,?
以下僅通過一個示例來說明自定義類裝載器的原理以及使用自定義裝載實現(xiàn)動態(tài)類型轉(zhuǎn)換:
package greeter;
public interface Greeter {
public void sayHello();
}
我在包greeter下定義了一個接口Greeter。
然后我實現(xiàn)一個自定義類裝載器GreeterClassLoader(如果是沒有特殊目的的加載,,直接繼承ClassLoader就可以了,,根本不用覆蓋任何方法):
//注:該實現(xiàn)是稍微有點復雜的,JDK文檔鼓勵使用另一種方法,,稍后提供這種方法并說明兩者之間的差異,。
public class GreeterClassLoader extends ClassLoader{
private String basePath; //自定義裝載作用的根目錄,,裝載器將在這個目錄下查找class文件
public GreeterClassLoader(String path){
this.basePath=path;
}
//覆蓋loadClass方法
@Override
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class result=null;
//看看這個類是不是已經(jīng)加載過了,,如果是直接返回緩存中的Class對象(放在JVM的堆中)
result=super.findLoadedClass(name);
if(result!=null){
System.out.println("class "+name+" has been loaded before.");
return result;
}
//上一步?jīng)]找到,,看看是不是系統(tǒng)類,委托給啟動類裝載器去裝載
result=super.findSystemClass(name);
if(result!=null){
System.out.println("found by system classloader.");
return result;
}else{
System.out.println("system loader can not find it.");
}
//都找不到,,直接讀取源文件
byte classdata[]=null;
//do not try to load system class
if(name.startsWith("java")){
System.out.println("i encountered a system class:"+name);
throw new ClassNotFoundException();
}
classdata=this.getClassData(name);
if(classdata==null){
System.err.println("can't load "+name);
}
System.out.println(name);
//從字節(jié)碼中解析出一個Class對象
result=defineClass(name, classdata, 0, classdata.length);
if(result==null){
System.out.println("Class format error.");
throw new ClassFormatError();
}
//是否需要解析
if(resolve){
this.resolveClass(result);
}
return result;
// return super.loadClass(name, resolve);
}
//從文件中讀取class文件的二進制數(shù)據(jù)
private byte[] getClassData(String name){
byte[] retArr=null;
//read the byte data of the class file
name=name.replace('.', '/');
String path=this.basePath+"/"+name+".class";
System.out.println(path);
try {
FileInputStream fin = new FileInputStream(path);
BufferedInputStream bis=new BufferedInputStream(fin);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int c=bis.read();
while(c!=-1){
baos.write(c);
c=bis.read();
}
bis.close();
System.out.println("read finished.");
retArr=baos.toByteArray();
} catch (FileNotFoundException ex) {
ex.printStackTrace();
return null;
}catch(IOException ex){
ex.printStackTrace();
return null;
}