面试官 : 要不你明天来具体讲讲双亲委派机制?
候选者:嗯,好的。
候选者:上次提到了:class 文件是通过「类加载器」装载至 JVM 中的
候选者:为了避免内存中存在多份同样的字节码,应用了双亲委派机制(它不会本人去尝试加载类,而是把申请委托给父加载器去实现,顺次向上)
候选者:JDK 中的本地办法类个别由根加载器(Bootstrp loader)装载,JDK 中外部实现的扩大类个别由扩大加载器(ExtClassLoader)实现装载,而程序中的类文件则由零碎加载器(AppClassLoader)实现装载。
候选者:这应该很好了解吧?
面试官:雀食(的确)!
面试官 : 顺着话题,我想问问,突破双亲委派机制是什么意思?
候选者:很好了解啊,意思就是:只有我加载类的时候,不是从 APPClassLoader->Ext ClassLoader->BootStrap ClassLoader 这个程序找,那就算是突破了啊
候选者:因为加载 class 外围的办法在 LoaderClass 类的 loadClass 办法上(双亲委派机制的外围实现)
候选者:那只有我自定义个 ClassLoader,重写 loadClass 办法(不按照往上开始寻找类加载器),那就算是突破双亲委派机制了。
面试官:这么简略?
候选者:嗯,就是这么简略
面试官 : 那你晓得有哪个场景毁坏了双亲委派机制吗?
候选者:最显著的就 Tomcat 啊
面试官:具体说说?
候选者:在初学时部署我的项目,咱们是把 war 包放到 tomcat 的 webapp 下,这意味着一个 tomcat 能够运行多个 Web 应用程序(:
候选者:是吧?
面试官:嗯..
候选者:那假如我当初有两个 Web 应用程序,它们都有一个类,叫做 User,并且它们的类全限定名都一样,比方都是 com.yyy.User。然而他们的具体实现是不一样的
候选者:那么 Tomcat 是如何保障它们是不会抵触的呢?
候选者:答案就是,Tomcat 给每个 Web 利用创立一个类加载器实例(WebAppClassLoader),该加载器重写了 loadClass 办法,优先加载以后利用目录下的类,如果以后找不到了,才一层一层往上找(:
候选者:那这样就做到了 Web 利用层级的隔离
面试官 : 嗯,那你还晓得 Tomcat 还有别的类加载器吗?
候选者:嗯,晓得的
候选者:并不是 Web 应用程序下的所有依赖都须要隔离的,比方 Redis 就能够 Web 应用程序之间共享(如果有需要的话),因为如果版本雷同,没必要每个 Web 应用程序都单独加载一份啊。
候选者:做法也很简略,Tomcat 就在 WebAppClassLoader 上加了个父类加载器(SharedClassLoader),如果 WebAppClassLoader 本身没有加载到某个类,那就委托 SharedClassLoader 去加载。
候选者:(无非就是把须要应用程序之间须要共享的类放到一个共享目录下嘛)
面试官:嗯..
候选者 :为了断绝 Web 应用程序与 Tomcat 自身的类,又有类加载器(CatalinaClassLoader) 来装载 Tomcat 自身的依赖
候选者 :如果 Tomcat 自身的依赖和 Web 利用还须要共享,那么还有类加载器(CommonClassLoader) 来装载进而达到共享
候选者:各个类加载器的加载目录能够到 tomcat 的 catalina.properties 配置文件上查看
候选者:我略微画下 Tomcat 的类加载结构图吧,不然有点形象
面试官:嗯,还能够,我听懂了,有点意思。
面试官 : 顺便,我想问下,JDBC 你不是晓得吗,据说它也是毁坏了双亲委派模型的,你怎么了解的。
候选者:Eumm,这个有没有毁坏,见仁见智吧。
候选者:JDBC 定义了接口,具体实现类由各个厂商进行实现嘛(比方 MySQL)
候选者:类加载有个规定:如果一个类由类加载器 A 加载,那么这个类的依赖类也是由「雷同的类加载器」加载。
候选者:咱们用 JDBC 的时候,是应用 DriverManager 进而获取 Connection,DriverManager 在 java.sql 包下,显然是由 BootStrap 类加载器进行装载
候选者 :当咱们应用 DriverManager.getConnection() 时,失去的肯定是厂商实现的类。
候选者:但 BootStrap ClassLoader 会能加载到各个厂商实现的类吗?
候选者:显然不能够啊,这些实现类又没在 java 包中,怎么可能加载失去呢
面试官:嗯..
候选者:DriverManager 的解决方案就是,在 DriverManager 初始化的时候,失去「线程上下文加载器」
候选者:去获取 Connection 的时候,是应用「线程上下文加载器」去加载 Connection 的,而这里的线程上下文加载器实际上还是 App ClassLoader
候选者:所以在获取 Connection 的时候,还是先找 Ext ClassLoader 和 BootStrap ClassLoader,只不过这俩加载器必定是加载不到的,最终会由 App ClassLoader 进行加载
面试官:嗯..
候选者:那这种状况,有的人感觉毁坏了双亲委派机制,因为原本明明应该是由 BootStrap ClassLoader 进行加载的,后果你来了一手「线程上下文加载器」,改掉了「类加载器」
候选者:有的人感觉没毁坏双亲委派机制,只是改成由「线程上下文加载器」进行类加载,但还是恪守着:「顺次往上找父类加载器进行加载,都找不到时才由本身加载」。认为”准则”上是没变的。
面试官:那我理解了
本文总结:
- 前置常识: JDK 中默认类加载器有三个:AppClassLoader、Ext ClassLoader、BootStrap ClassLoader。AppClassLoader 的父加载器为 Ext ClassLoader、Ext ClassLoader 的父加载器为 BootStrap ClassLoader。这里的父子关系并不是通过继承实现的,而是组合。
- 什么是双亲委派机制: 加载器在加载过程中,先把类交由父类加载器进行加载,父类加载器没找到才由本身加载。
- 双亲委派机制目标: 为了避免内存中存在多份同样的字节码(平安)
- 类加载规定: 如果一个类由类加载器 A 加载,那么这个类的依赖类也是由「雷同的类加载器」加载。
- 如何突破双亲委派机制: 自定义 ClassLoader,重写 loadClass 办法(只有不顺次往上交给父加载器进行加载,就算是突破双亲委派机制)
-
突破双亲委派机制案例: Tomcat
- 为了 Web 应用程序类之间隔离,为每个应用程序创立 WebAppClassLoader 类加载器
- 为了 Web 应用程序类之间共享,把 ShareClassLoader 作为 WebAppClassLoader 的父类加载器,如果 WebAppClassLoader 加载器找不到,则尝试用 ShareClassLoader 进行加载
- 为了 Tomcat 自身与 Web 应用程序类隔离,用 CatalinaClassLoader 类加载器进行隔离,CatalinaClassLoader 加载 Tomcat 自身的类
- 为了 Tomcat 与 Web 应用程序类共享,用 CommonClassLoader 作为 CatalinaClassLoader 和 ShareClassLoader 的父类加载器
- ShareClassLoader、CatalinaClassLoader、CommonClassLoader 的目录能够在 Tomcat 的 catalina.properties 进行配置
- 线程上下文加载器: 因为类加载的规定,很可能导致父加载器加载时依赖子加载器的类,导致无奈加载胜利(BootStrap ClassLoader 无奈加载第三方库的类),所以存在「线程上下文加载器」来进行加载。
欢送关注我的微信公众号【Java3y】来聊聊 Java 面试,对线面试官系列继续更新中!
【对线面试官 - 挪动端】系列 一周两篇继续更新中!
【对线面试官 - 电脑端】系列 一周两篇继续更新中!
原创不易!!求三连!!