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、父加载器所加载的类无法访问到子类加载器所加载的类

猜你喜欢

转载自blog.csdn.net/wzq6578702/article/details/79828510