一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

通過實例Java?ClassLoader原理

 zxy135 2010-09-16

通過實例Java ClassLoader原理

(2010-09-02 15:50:50)
標簽:

java

classloader

it

分類: Java技術
  注:本文是個人對java虛擬機規(guī)范提到的知識的一點總結。

      在Java中,類必須經過jvm使用類裝載器(class loader)裝載(load)之后才能使用。以主程序(Class A)為例,當jvm調用程序的main方法時,在沒有裝載A.class文件時,這是不可能的事情。

     裝載class文件是程序執(zhí)行的第一步,這跟linux下的程序執(zhí)行器(execve)裝載目標文件的原理是一樣的,jvm充當execve的角色,讀取 class文件的二進制數據,轉換成jvm內部能夠識別的數據結構。在這個過程中生成了一個A類關聯的Class對象,該Class對象記錄了A類相關的數據。

      一個類真正能使用要經過裝載、連接、初始化三個步驟。連接又可以分為“驗證”、”準備“、”解析 “三個步驟??傮w說來,由于class文件本身記錄了很多數據結構(常量池、字段信息、方法信息、引用信息等),必須要轉換成內部形式,這個過程就通過裝載來實現,但是,class文件自身并沒有完成鏈接,這跟C的目標文件有很大差別——其實也就是解析執(zhí)行和編譯執(zhí)行的區(qū)別了,裝載之后形成的內部結構還存在很多符號引用,需要resolve引用,這就是連接過程,原理跟C的鏈接是一樣——解決內部和外部符號引用。

     在連接過程,jvm試圖解析類A中出現的符號引用,比如A中定義了:

private B b=new B();

符號引用b是一個字段引用,B()是一個方法引用,并且B是定義在別的class文件的(一個類只能對應一個class文件),所以jvm試圖解析 b,這個過程同時要對B進行裝載(如果B并沒有被當前裝載器裝載),裝載過程一般是遞歸進行的,一直到達最高類層次(Object)。

    關于JVM是如何裝載、連接、初始化的,內容太多,詳細的信息要參考Java虛擬機規(guī)范。

    Java中(jdk  1.6版)有三種加載器,啟動加載器→擴展加載器(ExtClassLoader)→用戶程序加載器(AppClassLoader)。箭頭左邊的類是右邊類的父類。其中啟動類加載器對于我們是不可見的,用于加載java源碼包的類以及jdk安裝路徑下的類(rt.jar etc),而擴展類加載器用于加載ext目錄下的類,用戶類加載器則是加載普通的class文件。

   Java給我們提供了使用自定義類裝載器來裝載程序的可能,這有利于程序的擴展。想想看applet 的運行,JDBC驅動的裝載(典型的JDBC訪問語句Class,forName()),我們在編譯的時候能知道它們要裝載什么類型的類嗎?

以下僅通過一個示例來說明自定義類裝載器的原理以及使用自定義裝載實現動態(tài)類型轉換:

package greeter;


public interface Greeter {

    public void sayHello();
}

我在包greeter下定義了一個接口Greeter。

然后我實現一個自定義類裝載器GreeterClassLoader(如果是沒有特殊目的的加載,直接繼承ClassLoader就可以了,根本不用覆蓋任何方法):

//注:該實現是稍微有點復雜的,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;

        //看看這個類是不是已經加載過了,如果是直接返回緩存中的Class對象(放在JVM的堆中)
        result=super.findLoadedClass(name);
        if(result!=null){
            System.out.println("class "+name+" has been loaded before.");
            return result;
        }

       //上一步沒找到,看看是不是系統類,委托給啟動類裝載器去裝載
        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文件的二進制數據

     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;
        }
        return retArr;
    }


}

 

好了,我在greeter包下又建立了一個新類Hello,它繼承了Greeter接口:

public class Hello implements Greeter{

    public void sayHello() {
        System.out.println("hello.");
    }

}

以下是一個測試類,我想使用GreeterClassLoader加載Hello類:

public class Greet {
   
   

    public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException{

        //等待命令行輸入的字符串作為類的全限定名,我在這里輸入greeter.Hello
        Scanner scan=new Scanner(System.in);
        String path=scan.nextLine();
        GreeterClassLoader greeterLoader=new GreeterClassLoader("/build/classes");
        Class c=greeterLoader.loadClass(path, false);
        //c.newInstance();

        //輸出加載c的類加載器

        System.out.println(c.getClassLoader());

       
    }

輸出結果:

greeter.Hello
greeterLoader's loader: sun.misc.Launcher$AppClassLoader@19821f
found by system classloader.
greeter.Hello@1d58aae
Class c's loader: sun.misc.Launcher$AppClassLoader@19821f

注意到"found by system classloader."這段輸出,在loadclass中我們本來想直接讀取了class文件并使用defineClass加載字節(jié)碼,但是這段代碼沒有執(zhí)行(沒見到”read finished“信息),由此可見該類直接使用了super.findSystemClass(name)加載了,而這個方法本身調用了系統加載器(在這里是AppClassLoader),AppClassLoader是標準的用戶程序加載器。

如果我們把findSystemClass部分注釋掉,再重新測試,結果如下:

greeter.Hello
greeterLoader's loader: sun.misc.Launcher$AppClassLoader@19821f
E:/Project Area/NetBeans/NetBeansProjects/AlgorithmApps/build/classes/greeter/Hello.class
read finished.
greeter.Hello
E:/Project Area/NetBeans/NetBeansProjects/AlgorithmApps/build/classes/greeter/Greeter.class
read finished.
greeter.Greeter
i encountered a system class:java.lang.Object

Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
        at test.GreeterClassLoader.loadClass(GreeterClassLoader.java:58)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
        at test.GreeterClassLoader.loadClass(GreeterClassLoader.java:58)
        at test.Greet.main(Greet.java:40)
Caused by: java.lang.ClassNotFoundException
        at test.GreeterClassLoader.loadClass(GreeterClassLoader.java:51)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
        ... 11 more
Java Result: 1
成功生成(總時間:4 秒)

注意到在調用defineClass后,它同時也解析了接口Greeter,讀取了Greeter.class文件并解析。但是,在解析 Object(每個類的超類)時出錯了,因為java.lang.Object是由啟動類加載的,自定義類加載器找不到它的路徑,所以加載失敗。

這也是這種方式的一個不足之處。

 

現在我們看看JDK推薦的方式,覆蓋ClassLoader的findClass方法:

findClass
protected Class findClass(String name)
                      throws ClassNotFoundException使用指定的二進制名稱查找類。此方法應該被類加載器的實現重寫,該實現按照委托模型來加載類。在通過父類加載器檢查所請求的類后,此方法將被 loadClass 方法調用。默認實現拋出一個 ClassNotFoundException

我們寫了一個類如下:

public class GreeterClassLoaderNew extends ClassLoader{

    private String basepath;

    public GreeterClassLoaderNew(String path){
        this.basepath=path;
    }

    public GreeterClassLoaderNew(ClassLoader loader,String path){
        super(loader);
        this.basepath=path;
    }

    @Override
    public Class loadClass(String name) throws ClassNotFoundException {
        System.out.println("i am called.");
        return super.loadClass(name);
    }

 

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        byte[] classData=null;
        classData=this.getClassData(name);
        if(classData==null){
            throw new ClassNotFoundException();
        }
        Class c=this.defineClass(name, classData, 0, classData.length);
        return c;
    }

     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;
        }
        return retArr;
    }

}

 

然后運行測試:

 GreeterClassLoaderNew greeterLoader=new GreeterClassLoaderNew(pwd);
        System.out.println("greeterLoader's loader: "+greeterLoader.getClass().getClassLoader());
        Class c=greeterLoader.findClass(path);
        System.out.println(c.newInstance().toString());

        System.out.println("Class c's loader: "+c.getClassLoader());

結果:

greeter.Hello
greeterLoader's loader: sun.misc.Launcher$AppClassLoader@19821f
E:/Project Area/NetBeans/NetBeansProjects/AlgorithmApps/build/classes/greeter/Hello.class
read finished.
i am called.
i am called.
greeter.Hello@e09713
Class c's loader: test.GreeterClassLoaderNew@8813f2

注意”i am called.“指示了loadClass被調用了。我們開始是調用GreeterClassLoaderNew的findClass方法,當它加載完字節(jié)碼后,順便解析了Greet接口和Object類,在這個過程中jvm又調用了loadclass方法(所以我們看到了i am called),并且調用了兩次,loadClass是父類的方法,也就是說,它使用了父類裝載器(AppClassLoader)裝載了Greet接口和Object類。

從上可以看出,決定一個類的loader的是findClass方法,當一個類被加載器加載時,它隸屬于這個加載器的命名空間,不同加載器加載同一個類A,會產生兩個類A,這兩個A的對象是不能相互轉換的,它們的命名空間不一樣,導致它們屬于兩個不同的類型。

比如下面的轉換語句:

Hello h=(Hello)c.newInstance();

一眼看上去似乎這個語句可以成功執(zhí)行,但是結果是拋出異常。奇怪左邊跟右邊都是greeter.Hello的實例啊,竟然不能轉換。

——由于左邊的Hello類是AppClassLoader加載(定義加載器,它只會向它的父類請求加載,而并不知道實際上 GreeterClassLoaderNew已經加載了這個類)的,c是GreeterClassLoaderNew加載的,兩個類屬于不同的命名空間,執(zhí)行上面的語句將會產生異常信息"greeter.Hello cannnot be cast to greeter.Hello",看上去很詭異。

如果是Greeter ig=(Greeter)c.newInstance()則可以成功執(zhí)行。

這又是為什么呢?我們在使用GreeterClassLoader加載Hello類時,同時也加載了Greeter接口,注意這里的Greeter接口實際是GreeterClassLoader委托它的父類AppClassLoader加載的,所以Greeter是 AppClassLoader定義并加載的,而GreeterClassLoader只是它的初始化加載器,這個結論可以通過調用API Greeter.class.getClass().getClassLoader()查看定義類加載器證實。

具體的原因我現在還沒弄懂,大略分析如下:

jvm判別ig和c.newInstance()的類型,判斷能否進行轉換,發(fā)現c.newInstance的接口是由AppClassLoader加載的,跟左邊的一致,所以執(zhí)行了類型轉換。

具體的原因還需要深究。

 ------剛剛看了Inside java virtual machine的關于java類型安全和裝載約束的部分,感覺了解到一個比較正確的答案:ig是由AppClassLoader裝載的,而Greeter 接口也是由AppClassLoader裝載,并且Greeter在GreeterClassLoaderNew、AppClassLoader之間共享,雖然它們不是屬于同個命名空間,這樣一來,GreeterClassLoaderNew定義的Hello類就可以轉換成AppClassLoader 定義的Greeter接口。

我們可以作如下的驗證:

在GreeterClassLoaderNew中的loadClass截獲對Greeter的解析(因為我們自行加載了Hello類之后,JVM又試圖使用GreeterClassLoaderNew來加載Greeter接口,但這時它調用的方法是loadClass,而不是findClass,默認loadClass會將加載任務委派給它的父類AppClassLoader),仍然把它導向到findClass中,進行我們自己的加載:

 @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("i am called.");
        if(name.endsWith("Greeter")){
            return this.findClass(name);
        }
        return super.loadClass(name);
    }

好了,這樣的結果造成了Greeter也是由GreeterClassLoader定義的,并且AppClassLoader并不知道,所以在執(zhí)行

Greeter ig=(Greeter)c.newInstance();

時,它又加載了Greeter,并且因為此時GreeterClassLoader和AppClassLoader并沒有共享Greeter接口,所以這個轉換失敗了。

結果:

Exception in thread "main" java.lang.ClassCastException: greeter.Hello cannot be cast to greeter.Greeter

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    麻豆一区二区三区精品视频| 亚洲熟妇熟女久久精品 | 欧美成人黄色一级视频| 欧美日本精品视频在线观看| 黄片免费在线观看日韩| 免费人妻精品一区二区三区久久久| 亚洲最新中文字幕在线视频| 亚洲男人天堂成人在线视频| 日韩中文字幕人妻精品| 欧美高潮喷吹一区二区| 激情三级在线观看视频| 久久精品国产亚洲av久按摩| 国产亚州欧美一区二区| 色涩一区二区三区四区| 国产二级一级内射视频播放| 一级片二级片欧美日韩| 丝袜美女诱惑在线观看| 精品亚洲香蕉久久综合网| 老司机精品线观看86| 国产精品日韩欧美第一页| 日韩人妻免费视频一专区| 国产一区二区三区不卡| 欧美黑人暴力猛交精品| 精品国产91亚洲一区二区三区 | 激情中文字幕在线观看| 日韩中文字幕视频在线高清版| 欧美性欧美一区二区三区| 日韩性生活视频免费在线观看| 欧美一区二区口爆吞精| 国产又粗又硬又大又爽的视频| 粉嫩一区二区三区粉嫩视频| 国产精品福利一级久久| 女生更色还是男生更色| 国产欧美日韩在线精品一二区| 欧美视频在线观看一区| 亚洲天堂精品1024| 国产免费观看一区二区| 亚洲熟妇熟女久久精品| 深夜福利亚洲高清性感| 亚洲国产精品一区二区| 福利视频一区二区在线|