Java虚拟机可以安装多个类加载器,系统默认三个主要的类加载器,每个加载器负责加载特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader。
类加载器本身也是一个Java类,因为其他Java类的类加载器本身也要被类加载器加载,所以肯定有一个类加载器不是Java类,这便是BootStrap,BootStrap嵌套在JVM内核中。Java虚拟机中所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载器。
当Java虚拟机要加载一个类时,首先由当前线程的类加载器去加载线程中的第一个类,如果类A引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B,当然,也可以通过直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载 类时,又先委托给其上级类加载器,当所有祖宗类加载器都没有加载到类,则会回到发起者类加载器,如果还加载不了,则抛出ClassNotFoundException,而不会去寻找发起者的儿子去加载,因为没有getChild()方法。看图:
当Java虚拟机要加载一个类时,一般来说,一个加载请求由AppClassLoader发起,但是AppClassLoader并不会尝试去加载该类,而是把这个任务委托给他的父类,即ExtClassLoader,而ExtClassLoader也不会去加载,而是又委托给他的父类,即BootStrap,BootStrap已经是终极大boss了,他不能委托给其他人,只好自己去寻找class文件,如果他找到了class文件,则将之加载,当他找不到时,又把任务推给儿子,也就是ExtClassLoader,ExtClassLoader这时才会去寻找,如果他找到了class文件,则将之加载,当他找不到时,又把任务推给儿子,也就是AppClassLoader,如果他找到了class文件,则将之加载,当他找不到时,则抛出一个ClassNotFoundException,加载请求的发起者不会再去寻找自己的儿子让他加载。
看下面一段代码,相关分析都在注释里:
public static void main(String[] args) {
// 先得到类加载器,再拿到类加载器的字节码,最后根据字节码拿到类加载器的名称
System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
.getName());
// System的类加载器为null,说明System的类加载器是BootStrap
System.out.println(System.class.getClassLoader());
}
输出:
说明目前运行类的类加载器是AppClassLoader,如果我们把当前类打包成一个jar包,放在C:\Program Files\Java\jre1.8.0_31\lib\ext目录下(每个人不一样),我们再尝试运行,看输出:
这次变成了ExtClassLoader来加载类了,为什么呢?稍微分析一下就会很清楚,首先是AppClassLoader发起一个加载类的请求,这个请求层层上传,一直传到他的爷爷也就是BootStrap那里,他的爷爷没找到class文件又把请求发回给ExtClassLoader,因为C:\Program Files\Java\jre1.8.0_31\lib\ext文件夹中已经有了我们要加载的类,而这个文件夹又恰好由ExtClassLoader来负责加载,所以ExtClassLoader就把这个类加载了,消息就不再往儿子那里传递了,所以输出的是ExtClassLoader加载了类。
通过下面的代码,我们可以看出类加载器之间的父子关系:
@Test
public void test2(){
//拿到一个类加载器
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
//循环依次遍历loader的父类
while(loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
//最后打印出loader的老祖宗,null
System.out.println(loader);
}
输出: