JVM:类加载器·双亲委派模型
我达到目标的惟一的力量就是我的坚持精神。巴斯德
引导语
类加载器顾名思义,用于实现类的加载动作。
类加载器应用:OSGi、程序热部署、代码加密等。
类加载器之间的关系:双亲委派模型。
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。
启动类加载器(Bootstrap ClassLoader)
负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的。(C++语言实现)
扩展类加载器(Extension ClassLoader)
负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。(Java代码实现sun.misc.Launcher$ExtClassLoader)
扩展:JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。
应用程序类加载器(Application ClassLoader)
负责加载用户类路径(ClassPath)上所有的类库。(Java代码实现sun.misc.Launcher$AppClassLoader)
由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。
除了启动类加载器外,都应有父类加载器。
关系:一般不是继承的关系,而是组合的关系复用父加载器的代码。
原理:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
1. 如何在父加载器加载的类中,去调用子加载器去加载类?
此处引入线程上下文类加载器(Thread Context ClassLoader),通过java.lang.Thread类的setContext-ClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
Java中涉及SPI的加载基本都在采用该方式,如:JNDI、JDBC、JCE、JAXB和JBI。
SPI实现方式:
JDK(6)提供了java.util.ServiceLoader类,以META-INF/services中的配置信息,辅以责任链模式。
JDBC举例:
JDBC的Driver接口定义在JDK中,其实现有数据库的各个厂商提供。DriverManager类中要加载各个实现了Driver接口的类统一进行管理,Driver类位于JAVA_HOME中jre/lib/rt.jar中,应该由Bootstrap类加载器进行加载,而各个Driver的实现类位于各个服务商提供的jar包中。根据类加载机制,当被加载的类引用了另外一个类时,虚拟机就会使用装载第一个类的类加载器装在被引用的类,也就是说应该使用Bootstrap类加载器去加载各个厂商提供的Driver类。但是,Bootstrap类加载器只负责加载JAVA_HOME中jre/lib/rt.jar中所有的class,所以需要由子类加载器去加载Driver的实现类,这就破坏了双亲委派模型。
2. OSGi如何通过类加载器实现热部署(“即插即用”)?
OSGi实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。
说到OSGi,这里想到了“Spring DM”Spring动态模型。
3. Tomcat类加载器为什么破坏双亲委派?
tomcat是个web容器,有可能需要部署多个应用程序,不同的应用程序有可能会以来同一个第三方类库不同的版本。
Tomcat类加载图
1. OSGI类加载器
OSGI 框架根据 Bundle 的 MANIFEST.MF 文件中描述的数据信息进行解析处理 Bundle 间的依赖关系。
推荐阅读: