久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

通過實例Java?ClassLoader原理

 zxy135 2010-09-16

通過實例Java ClassLoader原理

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

java

classloader

it

分類: Java技術(shù)
  注:本文是個人對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;
        }
        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());

       
    }

輸出結(jié)果:

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)加載了,,而這個方法本身調(diào)用了系統(tǒng)加載器(在這里是AppClassLoader),,AppClassLoader是標準的用戶程序加載器。

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

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 秒)

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

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

 

現(xiàn)在我們看看JDK推薦的方式,,覆蓋ClassLoader的findClass方法:

findClass
protected Class findClass(String name)
                      throws ClassNotFoundException使用指定的二進制名稱查找類。此方法應(yīng)該被類加載器的實現(xiàn)重寫,,該實現(xiàn)按照委托模型來加載類,。在通過父類加載器檢查所請求的類后,此方法將被 loadClass 方法調(diào)用,。默認實現(xiàn)拋出一個 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());

結(jié)果:

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被調(diào)用了,。我們開始是調(diào)用GreeterClassLoaderNew的findClass方法,,當它加載完字節(jié)碼后,順便解析了Greet接口和Object類,,在這個過程中jvm又調(diào)用了loadclass方法(所以我們看到了i am called),,并且調(diào)用了兩次,loadClass是父類的方法,,也就是說,,它使用了父類裝載器(AppClassLoader)裝載了Greet接口和Object類。

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

比如下面的轉(zhuǎn)換語句:

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

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

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

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

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

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

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

具體的原因還需要深究。

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

我們可以作如下的驗證:

在GreeterClassLoaderNew中的loadClass截獲對Greeter的解析(因為我們自行加載了Hello類之后,JVM又試圖使用GreeterClassLoaderNew來加載Greeter接口,,但這時它調(diào)用的方法是loadClass,,而不是findClass,默認loadClass會將加載任務(wù)委派給它的父類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);
    }

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

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

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

結(jié)果:

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

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多