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