以前一直有这样一个疑惑:
都说在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。