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