深入理解JVM(五):类加载器的命名空间

github地址:

https://github.com/lishanglei/jvm_study.git

类加载器的命名空间:

每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父类加载器所加载的类组成

在同一个命名空间中,不会出现类的全限定名完全相同的两个类

在不同的命名空间中,有可能会出现类的全限定名完全相同的两个类

关于命名空间的重要说明:

  1. 子加载器所加载的类能够访问到父加载器所加载的类
  2. 父加载器所加载的类无法访问子加载器所加载的类
public class MyCat {

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

    public MySample() {
        System.out.println("MySample is loaded by :" +this.getClass().getClassLoader());

        //实例化MyCat对象前会先加载MyCat,会使用加载MySample类的类加载器进行加载
        new MyCat();
    }
}
public class MyTest17_1 {

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

        MyTest16 loader1 =new MyTest16("loader1");
        loader1.setPath("C:\\Users\\10025\\Desktop\\");
        Class<?> clazz = loader1.loadClass("com.jvm.class_loader.MySample");
        System.out.println("class: "+clazz.hashCode());

        //该行会实例化MySample对象
        Object object =clazz.newInstance();


    }

}

操作步骤:

编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。

1.删除项目中MySample.class文件,保留MyCat.class文件

运行结果:

findClass invoked
loadClassData invoked
class: 2133927002
MySample is loaded by :[ loader1 ]
MyCat is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2

结果分析:

MySample由loader1加载器进行加载,根据双亲委托模型,类加载器顺序:loader1->System->Extension->Bootstrap->Extension->System->loader1

MyCat由System系统类加载器进行加载,根据双亲委托模型,类加载器顺序:loader1->System->Extension->Bootstrap->Extension->System

2.重新编译,删除项目中MyCat.class文件,保留MySample.class文件

运行结果:

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

Exception in thread “main” java.lang.NoClassDefFoundError: com/jvm/class_loader/MyCat
at com.jvm.class_loader.MySample.(MySample.java:18)
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.jvm.class_loader.MyTest17_1.main(MyTest17_1.java:22)
Caused by: java.lang.ClassNotFoundException: com.jvm.class_loader.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

结果分析:

MySample由System系统加载器进行加载,根据双亲委托模型,类加载器顺序:loader1->System->Extension->Bootstrap->Extension->System

而MyCat在加载时会默认使用加载MySample的类加载器进行加载,根据双亲委托模型,类加载器顺序:System->Extension->Bootstrap->Extension->System,结果没有类加载器可以加载MyCat类

public class MyCat {

    public MyCat() {
        System.out.println("MyCat is loaded by :" +this.getClass().getClassLoader());

        System.out.println("from MyCat: "+MySample.class);
    }
}

还是上述代码,在MyCat增加一行代码 System.out.println("from MyCat: "+MySample.class);

操作步骤:

编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。

1.删除项目中MySample.class文件,保留MyCat.class文件

运行结果:

findClass invoked
loadClassData invoked
class: 2133927002
MySample is loaded by :[ loader1 ]
MyCat is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2

Exception in thread “main” java.lang.NoClassDefFoundError: com/jvm/class_loader/MySample
at com.jvm.class_loader.MyCat.(MyCat.java:17)
at com.jvm.class_loader.MySample.(MySample.java:18)
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.jvm.class_loader.MyTest17_1.main(MyTest17_1.java:22)
Caused by: java.lang.ClassNotFoundException: com.jvm.class_loader.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

结果分析:

MySample 由自定义类加载loader1加载,而MyCat由系统类加载器加载,不同的命名空间,导致MyCat在调用MySample.class属性时在系统类加载器的命名空间中找不到MySample的Class对象.即父加载器不能调用其子加载器命名空间中所加载的Class对象

public class MyCat {

    public MyCat() {
        System.out.println("MyCat is loaded by :" +this.getClass().getClassLoader());

        //System.out.println("from MyCat: "+MySample.class);
    }
}
public class MySample {

    public MySample() {
        System.out.println("MySample is loaded by :" +this.getClass().getClassLoader());

        //实例化MyCat对象前会先加载MyCat,会使用加载MySample类的类加载器进行加载
        new MyCat();
        System.out.println("from MySample :" +MyCat.class);
    }
}

注释掉MyCat中刚刚添加的那一行代码,在MySample中添加一行代码System.out.println(“from MySample :” +MyCat.class);

操作步骤:

编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。

1.删除项目中MySample.class文件,保留MyCat.class文件

**运行结果:**findClass invoked
loadClassData invoked
class: 2133927002
MySample is loaded by :[ loader1 ]
MyCat is loaded by :sun.misc.Launcher$AppClassLoader@18b4aac2
from MySample :class com.jvm.class_loader.MyCat

结果分析:

MySample由自定义类加载器loader1加载,而MyCat由系统类加载器加载.由于系统类加载器是自定义加载器的父加载器,在自定义加载器中是可以访问由父加载器加载的Class对象

public class MyPerson {

    private MyPerson myPerson;

    public void setMyPerson(Object myPerson) {
        this.myPerson =(MyPerson) myPerson;
    }
}
public class MyTest21 {

    public static void main(String[] args) throws Exception {

        MyTest16 loader1 =new MyTest16("loader1");
        MyTest16 loader2 =new MyTest16("loader2");

        loader1.setPath("C:\\Users\\10025\\Desktop\\");
        loader2.setPath("C:\\Users\\10025\\Desktop\\");

        Class<?> clazz1 = loader1.loadClass("com.jvm.class_loader.MyPerson");
        Class<?> clazz2 = loader2.loadClass("com.jvm.class_loader.MyPerson");

        System.out.println(clazz1==clazz2);

        Object object1 =clazz1.newInstance();
        Object object2 =clazz2.newInstance();

        Method method  =clazz1.getMethod("setMyPerson", Object.class);
        method.invoke(object1, object2);
    }
}

操作步骤:

编译项目,将编译后的com.jvm.class_loader文件夹复制一份到桌面。

1.删除项目中MyPerson.class文件

输出结果:

类加载器: loader1
类加载器: loader2
false
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.jvm.class_loader.MyTest21.main(MyTest21.java:33)
Caused by: java.lang.ClassCastException: com.jvm.class_loader.MyPerson cannot be cast to com.jvm.class_loader.MyPerson
at com.jvm.class_loader.MyPerson.setMyPerson(MyPerson.java:17)
… 5 more

结果分析:

由于项目中生成的MyPerson.class文件被删除,所以类加载loader1,loader2分别加载了MyPerson类到各自的命名空间中,且互相不可见.因此System.out.println(clazz1==clazz2);输出为false.

综上所述得出如下结论:

  1. 同一个命名空间内的类是相互可见的.
  2. 子加载器的命名空间包含所有父加载器的命名空间.因此由子加载器加载的类能看见父加载器加载的类.例如系统类加载器加载的类可以看见根类加载器加载的类
  3. 由父类加载器加载的类不能看见子加载器加载的类.
  4. 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见

双亲委托模型的优点:

  1. 确保java核心库的类型安全,所有的java引用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被加载到虚拟机中;如果这个加载过程是由Java引用自己的类加载器所完成的,那么很可能会在JVM中存在多个版本的java.lang.Object类,而且这些类之间还是不兼容的,互相不可见的(正式命名空间在发挥着作用).借助于双亲委托机制,java核心类库中的类加载工作都是由启动类加载器来完成,从而确保了java应用所使用的都是同一个版本的java核心类库,它们之间相互兼容.
  2. 可以确保java核心类库所提供的类不会被自定义的类所替代
  3. 不同的类加载器可以为相同全限定名的类创建额外的命名空间,相同限定名的类可以并存在java虚拟机中,只需要不同的类加载器来加载它们即可.不同的类加载器所加载的类之间是不兼容,不可见的,这就相当于在java虚拟机内部创建了一个又一个相互隔离的java类空间,这类技术在很多框架中得到了实际应用.
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值