JVM学习笔记4_命名空间

1. JVM三种类加载器路径获取

可以通过System.getProperty() 方法获取类加载器加载类的路径:

System.getProperty("sun.boot.class.path") //表示根类加载器加载路径
System.getProperty("java.ext.dirs") //表示扩展类加载器加载路径
System.getProperty("java.class.path") //表示应用类加载器加载路径

一般地,我们编写自定义的类,都是通过应用类加载器加载的。这是由类加载器的双亲委托机制决定的。
如果让根类加载器或扩展类加载器我们自定义的类,该如何实现呢? 看第二小节

2. 改变类的加载器

先来个小例子说明问题:

public class MyTest19 {

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

        //根类加载器加载类的路径
        System.out.println(System.getProperty("sun.boot.class.path"));
        //扩展类加载器加载类的路径
        System.out.println(System.getProperty("java.ext.dirs"));
        //应用类加载器加载类的路径
        System.out.println(System.getProperty("java.class.path"));

        MyClassLoader classLoader = new MyClassLoader("D:\\study\\study_code\\jvm\\dcj\\out\\production\\classes");

        Class<?> clazz = classLoader.loadClass("classloader3.MySample");
        System.out.println("clazz classLoader is :" + clazz.getClassLoader());

    }
}

运行结果:

D:\study\study_software\java\jdk\jre\lib\resources.jar;D:\study\study_software\java\jdk\jre\lib\rt.jar;D:\study\study_software\java\jdk\jre\lib\sunrsasign.jar;D:\study\study_software\java\jdk\jre\lib\jsse.jar;D:\study\study_software\java\jdk\jre\lib\jce.jar;D:\study\study_software\java\jdk\jre\lib\charsets.jar;D:\study\study_software\java\jdk\jre\lib\jfr.jar;D:\study\study_software\java\jdk\jre\classes
D:\study\study_software\java\jdk\jre\lib\ext;C:\windows\Sun\Java\lib\ext
D:\study\study_software\java\jdk\jre\lib\charsets.jar;D:\study\study_software\java\jdk\jre\lib\deploy.jar;D:\study\study_software\java\jdk\jre\lib\ext\access-bridge-64.jar;D:\study\study_software\java\jdk\jre\lib\ext\cldrdata.jar;D:\study\study_software\java\jdk\jre\lib\ext\dnsns.jar;D:\study\study_software\java\jdk\jre\lib\ext\jaccess.jar;D:\study\study_software\java\jdk\jre\lib\ext\jfxrt.jar;D:\study\study_software\java\jdk\jre\lib\ext\localedata.jar;D:\study\study_software\java\jdk\jre\lib\ext\nashorn.jar;D:\study\study_software\java\jdk\jre\lib\ext\sunec.jar;D:\study\study_software\java\jdk\jre\lib\ext\sunjce_provider.jar;D:\study\study_software\java\jdk\jre\lib\ext\sunmscapi.jar;D:\study\study_software\java\jdk\jre\lib\ext\sunpkcs11.jar;D:\study\study_software\java\jdk\jre\lib\ext\zipfs.jar;D:\study\study_software\java\jdk\jre\lib\javaws.jar;D:\study\study_software\java\jdk\jre\lib\jce.jar;D:\study\study_software\java\jdk\jre\lib\jfr.jar;D:\study\study_software\java\jdk\jre\lib\jfxswt.jar;D:\study\study_software\java\jdk\jre\lib\jsse.jar;D:\study\study_software\java\jdk\jre\lib\management-agent.jar;D:\study\study_software\java\jdk\jre\lib\plugin.jar;D:\study\study_software\java\jdk\jre\lib\resources.jar;D:\study\study_software\java\jdk\jre\lib\rt.jar;D:\study\study_code\jvm\dcj\out\production\classes;D:\Program Files\JetBrains\IntelliJ IDEA 2018.1.3\lib\idea_rt.jar
clazz classLoader is :sun.misc.Launcher$AppClassLoader@18b4aac2

前三行打印了三种不同的类加载器加载类的路径。第四行表明MySample类是由应用类加载器加载的。

观察第一行输出的最后,D:\study\study_software\java\jdk\jre\classes, 表明根类加载器会去该路径加载类,该路径其实就是$JAVA_HOME/jre/classes, 把 MyTest19 和 MySample 类 拷贝至 该目录下, 再次运行程序, 输出结果如下:

clazz classLoader is :null

表明MySample类由根类加载器加载了。

注意: 扩展类加载器不能直接去加载class文件,需要打成一个jar包,操作命令如下:

#将class文件打成一个jar包,跟tar命令非常类似
jar cvf test.jar  classloader3/MyTest20.class

#动态指定java.ext.dirs路径
java -Djava.ext.dirs=./ classloader3.MyTest21

这样,在运行MyTest21类的时候,扩展类加载器会去指定的路径寻找, MyTest20类就会被sun.misc.Launcher$ExtClassLoader类加载器所加载。

3. 类的命名空间
  • 每个类加载器都有自己的命名空间,命名空间是由该类加载器及其所有的父类加载器所加载的类组成
  • 在同一个命名空间里,不会出现类的完整名字相同的两个类(class对象)。
  • 在不同的命令空间里,有可能出现类的完整名字相同的两个类(class对象)。

同一个命名空间的类是相互可见的。子加载器的命名空间包含所有父加载器的命名空间。系统类加载器加载的类可以看见根类加载器加载的类。

public class MyTest20 {

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

        MyClassLoader loader1 = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\classes\\");
        MyClassLoader loader2 = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\classes\\");

        Class<?> clazz1 = loader1.loadClass("classloader3.MyCat");
        Class<?> class2 = loader2.loadClass("classloader3.MyCat");

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

        Object myCat =  clazz1.newInstance();
        Object myCat2 =  class2.newInstance();
	    
	    //这行可能抛异常
        Method method = clazz1.getMethod("setCat", Object.class);
        method.invoke(myCat, myCat2);
    }
}

当把 MyCat类class 文件放入对应位置, 并且将IDEA 工程下的 MyCat类class 文件删除后,打印结果为false。 因为 MyCat类由两个不同的类加载器加载了两次,他们位于两个不同的命名空间, 相互不可见。最后反射调用时,会出现一个异常:

Caused by: java.lang.ClassCastException: classloader3.MyCat cannot be cast to classloader3.MyCat

看着有点奇怪吧,本质上就是因为两个类在不同的命名空间,相互不可见。

总结:在运行期,一个java类是由该类的完全限定名(binary name)和 用于加载该类的定义类加载器(defining loader)共同决定的。如果同样名字的类由两个不同的加载器加载,那么这些类就是不同的, 尽管.class文件的字节码完全相同。

4 . 类加载器双亲委托模型的的好处
  • 确保java核心类的类型安全:所有的java应用至少都会引用java.lang.Object类,在运行期,java.lang.Object类会被加载到JVM中,如果这个加载过程由自定义的类加载器完成,可能存在多个版本的Object类,这些类是不兼容的,相互不可见(由命名空间导致)。借助双亲委托机制,java核心类库的类加载工作由启动类加载器完成,他们之间是相互兼容的。
  • 可以确保java核心类库所提供的类不会被自定义的类所替代。
  • 不同的类加载器可以为相同名称(binaryname)的类创建不同的命名空间。相同名称的类可以并存在java虚拟机中,只需要不同的java类加载器来加载他们即可。不同的类加载器所加载的类是不兼容的。
5 . 总结
  • 技术上,我们可以让根类加载器或者扩展类加载器加载我们自定义的类,需要将class文件放入对应的加载路径下(扩展类加载器需要加载jar包)。因此,main方法所在的类我们也可以做到让启动类加载器去加载。
  • 同一个命名空间的类是相互可见的。子加载器的命名空间包含所有父加载器的命名空间。系统类加载器加载的类可以看见根类加载器加载的类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值