国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
通過實(shí)例Java?ClassLoader原理

通過實(shí)例Java ClassLoader原理

(2010-09-02 15:50:50)
標(biāo)簽:

java

classloader

it

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


}

 

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

public class Hello implements Greeter{

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

}

以下是一個(gè)測(cè)試類,我想使用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)加載了,而這個(gè)方法本身調(diào)用了系統(tǒng)加載器(在這里是AppClassLoader),AppClassLoader是標(biāo)準(zhǔn)的用戶程序加載器。

如果我們把findSystemClass部分注釋掉,再重新測(cè)試,結(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
成功生成(總時(shí)間:4 秒)

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

這也是這種方式的一個(gè)不足之處。

 

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

findClass
protected Class findClass(String name)
                      throws ClassNotFoundException使用指定的二進(jìn)制名稱查找類。此方法應(yīng)該被類加載器的實(shí)現(xiàn)重寫,該實(shí)現(xiàn)按照委托模型來加載類。在通過父類加載器檢查所請(qǐng)求的類后,此方法將被 loadClass 方法調(diào)用。默認(rèn)實(shí)現(xiàn)拋出一個(gè) ClassNotFoundException

我們寫了一個(gè)類如下:

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

}

 

然后運(yùn)行測(cè)試:

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

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

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

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

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

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

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

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

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

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

具體的原因還需要深究。

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

我們可以作如下的驗(yàn)證:

在GreeterClassLoaderNew中的loadClass截獲對(duì)Greeter的解析(因?yàn)槲覀冏孕屑虞d了Hello類之后,JVM又試圖使用GreeterClassLoaderNew來加載Greeter接口,但這時(shí)它調(diào)用的方法是loadClass,而不是findClass,默認(rèn)loadClass會(huì)將加載任務(wù)委派給它的父類AppClassLoader),仍然把它導(dǎo)向到findClass中,進(jìn)行我們自己的加載:

 @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();

時(shí),它又加載了Greeter,并且因?yàn)榇藭r(shí)GreeterClassLoader和AppClassLoader并沒有共享Greeter接口,所以這個(gè)轉(zhuǎn)換失敗了。

結(jié)果:

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

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
分析AppClassLoader,ExtClassLoader 和URLClassLoader 的關(guān)系
一看你就懂,Java中的ClassLoader詳解
classloader加載class文件的原理和機(jī)制
JVM類加載機(jī)制詳解(二)類加載器與雙親委派模型
Java基礎(chǔ)中一些值得聊的話題(加載篇)
javaEE防盜版-java之類加載
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服