本文转载自:https://blog.csdn.net/qq_22912803/article/details/78065847
一、java的classLoader加载机制大家都比较清楚,就是委托机制,如下:
这种机制就是,加载一个类的时候,会一直向上委托,如果BootStrapClassLoader加载不到,然后再依次往下加载,如果最后SystemClassLoader也加载不到,则会抛出classNotFoundException。
二、现在我们来说说其他的,说之前先说说两个概念,这个是转述别人的(http://blog.csdn.net/yongche_shi/article/details/39695991)
1、有两个术语,一个叫“定义类加载器”,一个叫“初始类加载器”。
比如有如下的类加载器结构:
BootstrapClassloader
ExtClassloader
AppClassloader
-自定义clsloadr1
-自定义clsloadr2
如果用“自定义clsloadr1”加载java.lang.String类,那么根据双亲委派最终bootstrap会加载此类,那么bootstrap类就叫做该类的“定义类加载器”,而包括bootstrap的所有得到该类class实例的类加载器都叫做“初始类加载器”。
2、所说的“命名空间”,是指jvm为每个类加载器维护的一个“表”,这个表记录了所有以此类加载器为“初始类加载器”(而不是定义类加载器,所以一个类可以存在于很多的命名空间中)加载的类的列表,所以,题目中的问题就可以解释了:
CLTest是AppClassloader加载的,String是通过加载CLTest的类加载器也就是AppClassloader进行加载,但最终委派到bootstrap加载的(当然,String类其实早已经被加载过了,这里只是举个例子)。所以,对于String类来说,bootstrap是“定义类加载器”,AppClassloader是“初始类加载器”。根据刚才所说,String类在AppClassloader的命名空间中(同时也在bootstrap,ExtClassloader的命名空间中,因为bootstrap,ExtClassloader也是String的初始类加载器),所以CLTest可以随便访问String类。这样就可以解释“处在不同命名空间的类,不能直接互相访问”这句话了。
3、一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例。
4、那么由不同类加载器实例(比如-自定义clsloadr1,-自定义clsloadr2)所加载的classpath下和ext下的类,也就是由我们自定义的类加载器委派给AppClassloader和ExtClassloader加载的类,在内存中是同一个类吗?
所有继承ClassLoader并且没有重写getSystemClassLoader方法的类加载器,通过getSystemClassLoader方法得到的AppClassloader都是同一个AppClassloader实例,类似单例模式。
在ClassLoader类中getSystemClassLoader方法调用私有的initSystemClassLoader方法获得AppClassloader实例,在initSystemClassLoader中:
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
。。。
scl = l.getClassLoader();
AppClassloader是sun.misc.Launcher类的内部类,Launcher类在new自己的时候生成AppClassloader实例并且放在自己的私有变量loader里:
loader = AppClassLoader.getAppClassLoader(extclassloader);
值得一提的是sun.misc.Launcher类使用了一种类似单例模式的方法,即既提供了单例模式的接口getLauncher()又把构造函数设成了public的。但是在ClassLoader中是通过单件模式取得的Launcher 实例的,所以我们写的每个类加载器得到的AppClassloader都是同一个AppClassloader类实例。
这样的话得到一个结论,就是所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在方法区都是同一个类,堆中的Class实例也是同一个。
三、本文重点:
有一个上下文加载器,contextClassLoader,它存在于当前线程中,默认为SystemClassLoader,那么它有什么用呢?大部分情况下,我们加载类,jvm默认的委托加载体系是可以满足需求的,但是有一些特殊情况要另当别论,这就是rt.jar中的spi服务,最常见的就是jdbc了,它存在的包rt.jar是由BootStrapClassLoader加载的,但是在它的DriverManager中会调用spi具体的实现类,如mysql,但是具体的实现类是由SystemClassLoader加载的,根据隔离原则,DriverManager中是访问不到mysql具体方法的,那怎么办呢?具体先看DriverManager代码:
看到的是,会在DriverManager里使用contextClassLoader去加载具体mysql的实现类,所以BootStrapClassLoader就是mysql实现类的初始加载器,contextClassLoader(SystemClassLoader)是mysql的定义类加载器,这样的话,在BootStrapClassLoader命名空间中就会有mysql类,所以DriverManager里面就可以直接访问mysql类了,这其实打破了类的委托机制,这种机制普遍存在于好多第三方的工具中,如tomcat、spring中。
通过上面的两个案例分析,我们可以总结出线程上下文类加载器的适用场景:(https://www.2cto.com/kf/201609/551006.html)
1、 当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
2、当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。