1. 系统载入器简单介绍
Java虚拟机中能够安装多个类载入器,系统默认三个主要类载入器(BootStrap、ExtClassLoader、AppClassLoader)。每一个类载入器负责载入特定位置的类。
类载入器本身也是Java类(BootStrap除外)。由于它本身也要被类载入器载入,这样显然一定有第一个类载入器不是Java类。没错,正是BootStrap类载入器。它是由C++语言编写的。嵌在了Java虚拟机内核中的类载入器。当启动Java虚拟机时,它就被载入了。
2. 类载入器的结构与管辖范围
Java虚拟机中的全部类载入器採用具有父子关系的树形结构进行组织。在实例化一个类载入器对象时都须要为其指定一个父级类载入器对象,或者默认採用系统类载入器为其父级类载入。类载入器的树形结构与管辖范围例如以下图:
3. 类载入器的托付机制
当Java虚拟机载入某一个类时,究竟派出哪个类去载入呢?载入时遵循例如以下几个原则:
原则1:首先派出当前线程的类载入器载入类
原则2:每一个类载入器载入类时又先托付给其上级载入器。当全部的祖宗载入器没有载入到类,才回到发起者载入器。假设还没有载入到类,则将会抛出ClassNotFoundException。不会再去找发起者载入器的儿子,由于没有getChild方法,即使有,那么多个儿子(父类仅仅有一个),找哪一个呢?
原则3:假设类A引用了类B,那么Java虚拟机将使用载入类A的载入器来载入类B。
原则4:还能够直接指定某个载入器来载入类,如:ClassLoader.loadClass()。
注意,每一个ClassLoader本身分别仅仅能载入特定位置和文件夹中的类。但它们能够托付其他类载入器去载入类,这就是类载入器的托付模式。类载入器一级一级托付到BootStrap类载入器,当BootStrap无法载入当前所要载入的类时,然后才一级一级退回到子孙载入器去载入该类。
当退回到最初的类载入器时,假设它自己也不能完毕类的载入,那么会抛出ClassNotFound异常。
4. 举例1
首先我们定义一个空类TestClassLoader,代码例如以下:
package com.tgb.ClazzLoaders; public class TestClassLoader { }
然后我们再定义一个測试类TestMain来输出TestClassLoader类的类载入器名称:
package com.tgb.ClazzLoaders; public class TestMain { public static void main(String[] args) throws Exception { // 输出类TestClassLoader的当类载入器的名称 System.out.println(TestClassLoader.class.getClassLoader().getClass().getName()); } }
输出结果例如以下,为sun.misc.Launcher$AppClassLoader:
然后我们将类TestClassLoader打成一个jar包放到当前使用的jre\lib\ext文件夹下。例如以下图:
再次执行測试类TestMain,我们能够惊奇的发现输出结果变为了sun.misc.Launcher$ExtClassLoader。
这正验证了我们上面的类载入器托付机制。当载入TestClassLoader类时。当前的类载入器会向父级载入器一级一级托付,然后退回到ExtClassLoader时,它在自己的管辖范围内jre\lib\ext\*.jar。能够找到TestClassLoader这个类。
然后就将它载入了。
也是就说我们执行时用到的TestClassLoader类已经不是Eclipse中我们看到的这个类了,而是jre\lib\ext文件夹下我们打的TestClassLoader.jar中的类。
注意:一定要放到我们当前使用的jre文件夹下,否则不起作用,例如以下步骤可确认,右击项目---- >属性--->Run/Debug Settings找到自己的configuration,然后Edit查看JRE,例如以下图:
我将jre\lib\ext文件夹下我们打的TestClassLoader.jar删除掉。然后改造測试类,循环输出类TestClassLoader的当类载入器的名称,以及全部父类载入器的名称。代码例如以下:
package com.tgb.ClazzLoaders; public class TestMain { public static void main(String[] args) throws Exception { // 输出类TestClassLoader的当类载入器的名称,以及全部父类载入器的名称 ClassLoader loader = TestClassLoader.class.getClassLoader(); while (loader != null) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); } } }
结果例如以下:
5. 举例2
编写一个能打印出自己的类载入器和当前类载入器的父子结构关系链的MyServlet,正常公布后。看到打印结果例如以下:
把MyServlet文件打Jar包,放到ext文件夹中,重新启动tomcat.发现找不到Httpservlet的错误。
把servlet.jar也放到ext文件夹中.问题攻克了。打印的结果是ExtclassLoader。
由此说明,父级类载入器载入的类无法引用仅仅能被子级类载入器载入的类。例如以下图:
6. 总结
系统默认三个主要类载入器(BootStrap、ExtClassLoader、AppClassLoader),每一个类载入器负责载入特定位置的类。
Java虚拟机中的全部类载入器採用具有父子关系的树形结构进行组织。类载入器有一定的托付机制。
此外。我们能够自己定义自己的类载入器(继承ClassLoader)。然后指定类载入器的管辖范围(载入文件夹),然后我们就能够在类载入的时候对类进行一些特殊处理(比如加密)。