JAVA 不同类载入器命名空间的理解

Posted 大浪不惊涛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA 不同类载入器命名空间的理解相关的知识,希望对你有一定的参考价值。

 

都说在JAVA中,由不同类载入器载入的类在虚拟机中位于不同的命名空间下,不同命名空间下的类相互不可见。

 

这让我产生了一个迷惑:假设有一个类A使用了java.util.List类,为什么在执行时会没有错误。由于依照类载入的双亲委派机制,自己写的类A一般由系统类载入器载入,而java.util.List肯定是由启动类载入器(也叫Root类载入器)载入的,所以这两个类应该不在一个命名空间下。那在执行时为什么类A还 是能訪问到java.util.List?

           如今搞明确了,原因例如以下:

           首先我们明确。每个JAVA类经过载入后。在虚拟机中都有一个相应的类型。

 

           再有下面概念:假设类A被系统类载入器载入,那么该系统类载入器就是此A在虚拟机中相应类型的初始类载入器

           Java虚拟机为每一个类载入器维护了一个表,当中记录了将该类载入器作为初始类载入器的全部类型。

在载入一个类时,虚拟机使用这些列表来决定是否一个类已经被特定的类载入器载入过了(假设该类型在当前类载入器的列表中,就说明已经载入过了。就不再载入)。

 

           再回到刚到A使用java.util.List的样例,当A被载入后。解析到A使用了List,就会请求载入java.util.List。依据类的载入原理及双亲委派机制。会先请类A的类载入器,即系统类载入器载入java.util.List。系统类当然载入不了这个List,所以它会委派给自己的父载入器。即扩展类载入器;同理。终于会由依据类载入器载入这个java.util.List,并成功返回。

          依据Java虚拟机规范规定。在这个过程中涉及的全部类载入器--即从系统类载入器到根类载入器间,參与过载入的,都被标记为该类型的初始类载入器。

换句话说。java虚拟机为在第一个类载入器维护的表中加入一个类型,用来标明此载入器是该类型的初始类载入器。

 

          这样就不难理解类A为何能够使用java.util.List。

虽然它们不是由一个载入器载入的,由于在系统类载入器的表中。即维护了类型A,也维护了类型List。

 

一、定义类加载器、初始类加载器

类加载器结构:

  • bootstrap
    • ExtClassloader
      • AppClassloader
        • 自定义clsloadr1
        • 自定义clsloadr2

如果用“自定义clsloadr1”加载java.lang.String类,那么根据双亲委派最终bootstrap会加载此类,那么bootstrap类就叫做该类的“定义类加载器”,而包括bootstrap的所有得到该类class实例的类加载器都叫做“初始类加载器”。

 

二、命名空间

命名空间是指jvm为每个类加载器维护的一个“表”,这个表记录了所有以 此类加载器 为 “初始类加载器”(而不是定义类加载器,所以一个类可以存在于很多的命名空间中)加载的类的列表。

例如:
CLTest是AppClassloader加载的String通过加载CLTest的类加载器,也就是AppClassloader进行加载,但最终委派到bootstrap加载的(当然,String类其实早已经被加载过了,这里只是举个例子)。所以,对于String类来说,bootstrap是“定义类加载器”,AppClassloader是“初始类加载器”。根据刚才所说,String类AppClassloader命名空间中(同时也在bootstrapExtClassloader的命名空间中,因为bootstrap,ExtClassloader也是String的初始类加载器),所以CLTest可以随便访问String类。这样就可以解释“处在不同命名空间的类,不能直接互相访问”这句话了。

 

三、

一个类由不同的类加载器实例加载的话,会在方法区产生两个不同的类彼此不可见,并且在中生成不同Class实例

 

四、

那么由不同类加载器实例(比如-自定义clsloadr1,-自定义clsloadr2)所加载的classpath下和ext下的类,也就是由我们自定义的类加载器委派给AppClassloader和ExtClassloader加载的类,在内存中是同一个类吗?

所有继承ClassLoader并且没有重写getSystemClassLoader方法的类加载器,通过getSystemClassLoader方法得到的AppClassloader都是同一个AppClassloader实例,类似单例模式。

在ClassLoader类中getSystemClassLoader方法调用私有的initSystemClassLoader方法获得AppClassloader实例,在initSystemClassLoader中:

sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
...
scl = l.getClassLoader();

AppClassloader是sun.misc.Launcher类的内部类,Launcher类在new自己的时候生成AppClassloader实例并且放在自己的私有变量loader里:

loader = AppClassLoader.getAppClassLoader(extclassloader);

值得一提的是sun.misc.Launcher类使用了一种类似单例模式的方法,即既提供了单例模式的接口getLauncher(),又把构造函数设成了public的。但是在ClassLoader中是通过单件模式取得的Launcher 实例的,所以我们写的每个类加载器得到的AppClassloader都是同一个AppClassloader类实例

这样的话得到一个结论,就是所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在 方法区 都是 同一个类,堆中 的 Class实例 也是同一个

 

以上是关于JAVA 不同类载入器命名空间的理解的主要内容,如果未能解决你的问题,请参考以下文章

java语言安全机制及装载器体系结构

11类加载器的命名空间

php设计模式--命名空间与自动载入

PHP 类的命名空间 和自动载入

JAVA Class25

类加载器释疑