一篇图文彻底弄懂类加载器与双亲委派机制原创
导读
1.类加载器是怎么被创建出来的?
2. 什么是双亲委派机制?为什么要有这种机制?
3. 什么是线程上线文类加载器
1、类加载器的加载流程
1.1、启动类加载器
sun.misc.Launcher
。 这个类包含了两个静态内部类:-
ExtClassLoader:扩展类加载器内部类,下面会讲;
-
AppClassLoader:应用程序类加载器内部类,下面会讲
可以反编译rt.jar文件查看详细代码:
2 3 4 5 6 7 8 9 10 11 12 13 14 |
ExtClassLoader extClassLoader; try { extClassLoader = ExtClassLoader.getExtClassLoader(); } catch (IOException iOException) { throw new InternalError("Could not create extension class loader", iOException); } try { this.loader = AppClassLoader.getAppClassLoader(extClassLoader); } catch (IOException iOException) { throw new InternalError("Could not create application class loader", iOException); } Thread.currentThread().setContextClassLoader(this.loader); ... |
String.class.getClassLoader()
获取启动类的引用,会返回 null
;问题:
启动类加载器,扩展类加载器和应用类加载器都是由谁加载的?
启动类加载器是JVM的内部实现,在JVM申请好内存之后,由JVM创建这个启动类加载器
扩展类加载器和应用程序类加载器是由启动类加载器加载进来的;
说说以下代码输出什么:
1
2
3
4
5
6
7public static void main(String[] args) {
System.out.println("加载当前类的加载器:" + TestClassLoader.class.getClassLoader());
System.out.println("加载应用程序类加载器的加载器"
+ TestClassLoader.class.getClassLoader().getClass().getClassLoader());
System.out.println("String类的启动类加载器" + String.class.getClassLoader());
}
1.2、扩展类加载器
1.3、应用程序类加载器
getSystemClassLoader
方法得到应用程序类加载器。2、类加载器的双亲委派机制
2.1、双亲委派机制原理
双亲委派模型在JDK1.2之后被引入,并广泛使用,这不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。 我们也可以覆盖对应的方式,实现自己的加载模型。
-
一个类加载器收到了类加载请求,不会自己立刻尝试加载类,而是把请求委托给父加载器去完成,每一层都是如此,所有的加载请求最终都传递到最顶层的类加载器进行处理;
-
如果父加载器不存在了,那么尝试判断有没有被启动类加载器加载;
-
如果的确没有被夹在,则再自己尝试加载。
问题:
为什么要有这么复杂的双亲委派机制?
如果没有这种机制,我们就可以篡改启动类加载器中需要的类了,如,修自己编写一个
java.lang.Object
用自己的类加载器进行加载,系统中就会存在多个Object类,这样Java类型体系最基本的行为也就无法保证了。
2.2、双亲委派机制处理流程
java.lang.ClassLoader.loadClass()
方法的实现:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } |
-
可见性原则:
-
应用类加载器是可以读取到由扩展类加载器和启动类加载器加载进来的Class的;
-
扩展类加载器是可以读取到由启动类加载器加载进来的Class的;
-
唯一性:
-
类是唯一的,没有重复的类;
2.3、类加载器和Class实例的题外话
-
类加载器:负责管理各个存储区的类信息,如加载和卸载类信息;
-
Class实例:负责对接外部需求,如果外部有人想查看里面的类信息,则需要通过Class实例来获取;
3、其他非双亲委派模型的案例
3.1、JDK 1.0遗留问题
loadClass()
方法,而我们知道,在JDK1.2以后,loadClass里面就是双亲委派机制的实现代码,此时,要实现自定义类加载器,需要重新findClass()类即可。3.2、线程上下文类加载器
-
/common/:该目录下的类库可被Tomcat和所有的WebApp共同使用;
-
/server/:该目录下的类库可被Tomcat使用,但对所有的WebApp不可见;
-
/shared/:该目录下的类库可被所有的WebApp共同使用,但对Tomcat自己不可见;
/WebApp/WEB-INF
目录中: 这里面的类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。 为了实现以上各个目录的类库可见性效果,Tomat提供了如下的自定义类加载器:
shared
目录中。beanClassLoader
字段,这个是bean的类加载器,在prepareBeanFactory()方法中做了初始化:
|
|
2 3 4 5 |
@Nullable public ClassLoader getClassLoader() { return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader()); } |
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static ClassLoader getDefaultClassLoader() { ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable ex) { // Cannot access thread context ClassLoader - falling back... } if (cl == null) { // No thread context class loader -> use class loader of this class. cl = ClassUtils.class.getClassLoader(); if (cl == null) { // getClassLoader() returning null indicates the bootstrap ClassLoader try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable ex) { // Cannot access system ClassLoader - oh well, maybe the caller can live with null... } } } return cl; } |
|
|
beanClassLoader
,线程上下文类加载器来加载Bean Class实例。3.3、模块热部署
References
https://stackoverflow.com/questions/8387989/where-are-static-methods-and-static-variables-stored-in-java
https://www.geeksforgeeks.org/classloader-in-java/
欢迎关注微信公众号《Java架构杂谈》。