欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
通過(guò)實(shí)例Java?ClassLoader原理

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

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

java

classloader

it

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


}

 

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

public class Hello implements Greeter{

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

}

以下是一個(gè)測試類(lèi),我想使用GreeterClassLoader加載Hello類(lèi):

public class Greet {
   
   

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

        //等待命令行輸入的字符串作為類(lèi)的全限定名,我在這里輸入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的類(lèi)加載器

        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中我們本來(lái)想直接讀取了class文件并使用defineClass加載字節碼,但是這段代碼沒(méi)有執行(沒(méi)見(jiàn)到”read finished“信息),由此可見(jiàn)該類(lèi)直接使用了super.findSystemClass(name)加載了,而這個(gè)方法本身調用了系統加載器(在這里是AppClassLoader),AppClassLoader是標準的用戶(hù)程序加載器。

如果我們把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
成功生成(總時(shí)間:4 秒)

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

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

 

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

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

我們寫(xiě)了一個(gè)類(lèi)如下:

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

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

比如下面的轉換語(yǔ)句:

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

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

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

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

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

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

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

具體的原因還需要深究。

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

我們可以作如下的驗證:

在GreeterClassLoaderNew中的loadClass截獲對Greeter的解析(因為我們自行加載了Hello類(lèi)之后,JVM又試圖使用GreeterClassLoaderNew來(lái)加載Greeter接口,但這時(shí)它調用的方法是loadClass,而不是findClass,默認loadClass會(huì )將加載任務(wù)委派給它的父類(lèi)AppClassLoader),仍然把它導向到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);
    }

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

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

時(shí),它又加載了Greeter,并且因為此時(shí)GreeterClassLoader和AppClassLoader并沒(méi)有共享Greeter接口,所以這個(gè)轉換失敗了。

結果:

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

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

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久