Java的三个类加载器
- Bootstrap classLoader (启动类加载器)
- ExtClassLoader (扩展类加载器)
- AppClassLoader (应用类加载器)
三者为上下级关系,如图:
产生机制的原因
我们在写类的时候,由于类名限制不多,可能会出现我们写的类和java自带的类重名的情况。
极端举例,我们写了一个java.lang.String类,与java自带的冲突了。此时,我们就需要一套处理这个问题的机制,即为
-- 双亲委派机制 。
首先,当我们加载一个类的时候,JVM并不会马上加载并起作用,而是会先判断java中是否已存在该名的类。如果有重名的情况下,出于安全考量,是绝对不会使用我们自己定义的类。比如我们自己定义了一个java.lang.String类,而我们代码中大量使用了java自带的java.lang.String类中的方法,此时如果加载我们自定义的类,那么代码就无法运行了。
三种加载器的加载位置
- Bootstrap classLoader (启动类加载器):负责加载 jdk 核心类如 rt.jar(包含java.lang)
- ExtClassLoader (扩展类加载器):主要负责加载 jre/lib/ext 目录下的一些扩展的jar。
- AppClassLoader (应用类加载器):主要负责加载环境变量classpath下的类
委派机制
- 当我们的应用类加载器加载类的时候,先检查自己是否加载过,如果有那就无需再加载了。如果没有加载过,也不会马上加载,而会委托我们的上一级 -扩展类加载器 加载 (通过调用父加载器的loadClass方法)。
- 而扩展类加载器先检查自己是否加载过,如果有那就无需再加载了。如果没有加载过,也不会马上加载,而是会委托上一级 -启动类加载器 加载。
- 启动类加载器如果发现这个类在他的加载位置已经存在,就会将这个类加载到内存中。如果找不到此类,就会往下走,走到扩展类加载器
- 扩展类加载器如果发现这个类在他的加载位置已经存在,就会将这个类加载到内存中。如果找不到此类,就会往下走,走到应用类加载器
- 应用类加载器如果发现这个类在他的加载位置已经存在,就会将这个类加载到内存中。如果找不到此类,会抛出异常:ClassNotFoundException。
演示机制
在 java8 目录,jre 目录下新建文件夹 classes:
将自己编译过的Test.class文件放入classes文件夹
(lib中放的都是jar包,单独的 class文件放入我们自己创建的 classes文件夹中)
代码内容如下:
public class Test {
public static void main(String[] args) {
System.out.println("aaaa");
}
}
以上是为了让启动类加载器可以找到该类。
接着再将编译过 Test.class 放在 扩展类加载器 可以加载到的位置:
修改后的代码内容:
public class Test {
public static void main(String[] args) {
System.out.println("bbbb");
}
}
最后修改代码为如下,并编译:
public class Test {
public static void main(String[] args) {
System.out.println("cccc");
}
}
接着,在任意位置,执行我们的Test.class文件, 结果如下
结果与机制描述相符合。
将 jre/classes 的 Test.class 删除, 执行输出 "bbbb"
再将 jre/lib/ext 下的 Test.class 删除, 执行输出 "cccc"
Scala中没有双亲委派机制
object Test {
def main(args: Array[String]): Unit = {
val hashMap = new java.util.HashMap //调用的子包中的HashMap类
}
package java{
package util {
class HashMap {
println("I am not java.util.HashMap...")
}
}
}
执行如上代码:
由此可见,双亲委派机制在Scala中并 不起作用。
那么,我们应该如何调用原本 java.util.HashMap 呢?
很简单,只需要在前面加上 "_root_" :
//这样可以访问到java.util中的类
val hashmap = new _root_.java.util.HashMap