jvm原理(15)類加載器命名空間實戰剖析與透徹理解

新建類MyTest17_1:

public class MyTest17_1 {
    public static void main(String[] args)  throws Exception{
        MyTest16 loader1 = new MyTest16("loader1");
        loader1.setPath("E:\\data\\classes\\");
        Class<?> clazz = loader1.loadClass("com.twodragonlake.jvm.classloader.MySample");
        System.out.println("class :"+clazz.hashCode());
        //如果註釋掉改行,那麼並不會實例化MySample對象,即MySample構造方法不會被調用
        //因此不會實例化MyCat對象,既沒有對MyCat進行主動使用,這裏就不會加載MyCat class
        Object object = clazz.newInstance();
    }
}

和MyTest17不同的是我們指定了Path,運行結果還是:

class :1735600054
MySample is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2

ok,現在我們把當前工程下的MySample和MyCat的class文件刪除掉,然後copy一份到E:\data\classes\下邊,運行程序:

findClass invoked com.twodragonlake.jvm.classloader.MySample【加載MySample時MyTest16的打印】
 this.classLoaderName : loader1                             【MySample是由MyTest16加載,MyTest16的打印】
class :2133927002                                           【MyTest17_1的打印】
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6   【MySample構造器】
findClass invoked com.twodragonlake.jvm.classloader.MyCat   【MySample的構造器new MyCat的時候要先加載MyCat】
 this.classLoaderName : loader1                              【MyCat是由MyTest16加載的】
MyCat is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6  【MyCat構造器的打印】

好,我們繼續做一個實驗,重新編譯當前工程,讓MyCat和MySample的class文件出現,然後刪除MyCat的class文件,注意此時MySample在當前工程和【E:\data\classes\】下都有一份,而MyCat只在【E:\data\classes\】有一份,當前工程不存在MyCat的class文件,運行程序:

class :1735600054
Exception in thread "main" java.lang.NoClassDefFoundError: com/twodragonlake/jvm/classloader/MyCat
MySample is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
    at com.twodragonlake.jvm.classloader.MySample.<init>(MySample.java:6)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at com.twodragonlake.jvm.classloader.MyTest17_1.main(MyTest17_1.java:11)
Caused by: java.lang.ClassNotFoundException: com.twodragonlake.jvm.classloader.MyCat
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 7 more

分析:【 Class

findClass invoked com.twodragonlake.jvm.classloader.MySample
 this.classLoaderName : loader1
class :2133927002
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2

首先MySample在當前工程已經被刪除了,所以應用類加載器無法加載,會使用我們自定義的類加載器MyTest16去加載MySample,因此打印:

findClass invoked com.twodragonlake.jvm.classloader.MySample
 this.classLoaderName : loader1

之後【class :2133927002】是MyTest17_1的打印,在MySample的構造器new MyCat的時候,加載器了MySample的加載器(MyTest16)會嘗試加載MyCat,MyTest16自己並不會去加載MyCat,它首先會委託應用類加載器去加載,,應用類加載器能不能加載呢?答案是可以加載,因爲MyCat在當前classPath下(當前工程裏邊的,不是【E:\data\classes\】下邊的),所以後邊不會出現自定義加載MyTest16的log日誌,之後直接打印MyCat構造器的輸出【MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2】。MySample和MyCat是由2個不同的加載器加載出來的。

ok,繼續下一個實驗:我們在MyCat 構造器裏邊加入一行代碼:

public class MyCat {
    public MyCat(){
        System.out.println("MyCat is loaded by : "+this.getClass().getClassLoader());
        System.out.println("from MyCat : "+MySample.class);//加入打印MySample的一行代碼
    }
}

重新build當前工程讓MySample和MyCat文件出現在當前工程下,然後copy MyCat的class文件到【E:\data\classes\】下邊,之後刪除當前工程下的MySample的class文件,運行MyTest17_1程序。打印如下:

findClass invoked com.twodragonlake.jvm.classloader.MySample
 this.classLoaderName : loader1
Exception in thread "main" java.lang.NoClassDefFoundError: com/twodragonlake/jvm/classloader/MySample
    at com.twodragonlake.jvm.classloader.MyCat.<init>(MyCat.java:6)
    at com.twodragonlake.jvm.classloader.MySample.<init>(MySample.java:6)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at com.twodragonlake.jvm.classloader.MyTest17_1.main(MyTest17_1.java:11)
Caused by: java.lang.ClassNotFoundException: com.twodragonlake.jvm.classloader.MySample
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 8 more
class :2133927002
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2

分析:
首先這個例子和上一個例子只有一個地方不同就是在MyCat的構造器裏邊加了一行打印MySample的代碼,而且MySample和MyCat是由不同的類加載器加載的:

MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6  【MySample由MyTest16加載】
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2                【MyCat由AppClassLoader加載】

但是爲什麼出現MySample的ClassNotFoundException的異常呢,原因就是類的命名空間:
這裏寫圖片描述
即,子加載器可以看到父加載器加載的類,但是父加載器看不到子加載器加載的類,這個例子當中,MySample由子加載器MyTest16加載,MyCat由父加載器AppClassLoader加載,因此在父加載器裏邊看不到子加載器MyTest16加載的類MySample,所以拋出ClassNotFoundException異常。

ok,最後一個例子,
MySample(加入MyCat的打印) :

public class MySample {
    public MySample(){
        System.out.println("MySample is loaded by : "+this.getClass().getClassLoader());
        new MyCat();
        System.out.println("form MySample :"+MyCat.class);
    }
}

MyCat (註釋掉MySample的打印):

public class MyCat {
    public MyCat(){
        System.out.println("MyCat is loaded by : "+this.getClass().getClassLoader());
       // System.out.println("from MyCat : "+MySample.class);
    }
}

重新buid當前工程,生成MyCat 和MySample的class文件,拷貝一份到【E:\data\classes\】下邊,然後刪除當前工程的MySample的class文件,運行MyTest17_1程序,打印結果:

findClass invoked com.twodragonlake.jvm.classloader.MySample
 this.classLoaderName : loader1
class :2133927002
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
form MySample :class com.twodragonlake.jvm.classloader.MyCat

分析:
首先MySample的class不在當前工程下,因此會使用自定義加載器MyTest16加載,MySample的構造器裏邊出現new MyCat,因此會使用子加載器MyTest16的父加載器應用類加載器加載MyCat,之後【System.out.println(“form MySample :”+MyCat.class);】這行代碼是在子類加載器MyTest16裏邊出現,由於子類加載器可以看到父類加載器加載的類,因此不會拋出異常。

關於命名空間的重要說明
1、子加載器所加載的類能夠訪問到父加載器所加載的類
2、父加載器所加載的類無法訪問到子類加載器所加載的類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章