面试题:什么时候要打破双亲委派机制

来自社群的两个面试题,其实也是两个基础的 面试题,大家一定要掌握

社群问题:

先说下第一题的结论

场景1:

如果委托类没有实现接口的话,就不能使用newProxyInstance方法,进而不能使用JDK动态代理

场景2:

Cglib是针对类来实现代理的,对指定的目标类生成一个子类,通过方法拦截技术拦截所有父类方法的调用。因为是生成子类,所以就不能用在final修饰的类上。

综合起来,就是 被final修饰的类 ,不可以被spring代理

接下来就是第二题:如何打破双亲委托机制?

什么是双亲委派机制

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

类加载器的类别

BootstrapClassLoader(启动类加载器)

c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

ExtClassLoader (标准扩展类加载器)

java编写,加载扩展库,如classpath中的jre ,javax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。

AppClassLoader(系统类加载器)

java`编写,加载程序所在的目录,如`user.dir`所在的位置的`class

CustomClassLoader(用户自定义类加载器)

java编写,用户自定义的类加载器,可加载指定路径的class文件

源码分析

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先检查这个classsh是否已经加载过了Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {// c==null表示没有加载,如果有父类的加载器则让父类加载器加载if (parent != null) {c = parent.loadClass(name, false);} else {//如果父类的加载器为空 则说明递归到bootStrapClassloader了//bootStrapClassloader比较特殊无法通过get获取c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c == null) {//如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载classlong t1 = System.nanoTime();c = findClass(name);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,如果加载过了,则不需要重新加载。保证了数据安全。

  ②保证核心.class不被篡改。通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。这样则保证了Class的执行安全。

如何打破双亲委派(/如何破坏双亲委派)

这个问题很经典,面试如果问到JVM,这个问题大概率会被问到,这个时候就轮到给面试官好感,以及涨工资的时刻

当然可以破坏了,我们知道类的加载方式默认是双亲委派,如果我们有一个类想要通过自定义的类加载器来加载这个类,而不是通过系统默认的类加载器,说白了就是不走双亲委派那一套,而是走自定义的类加载器

我们知道双亲委派的机制是ClassLoader中的loadClass方法实现的,打破双亲委派,其实就是重写这个方法,来用我们自己的方式来实现即可

当然这里要注意一下,Object.class这是对象的顶级类,改变类的类加载器的时候要注意,如果全部改了,Object.class就找不到了,加载不了了

所以呢,这里重写的时候,要注意分类解决,把你想要通过自定义类加载器加载的和想通过默认类加载器加载的分隔开

如果不想打破双亲委派模型,就重写ClassLoader类中的findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。

而如果想打破双亲委派模型则需要重写ClassLoader类loadClass()方法(当然其中的坑也不会少)。典型的打破双亲委派模型的框架和中间件有tomcat与osgi

举例:tomcat是如何打破双亲委派的

通过自定义加载器的过程,我们知道,实现自定义的classloader,需要重新loadClass以及findClass,我们先看

webappsClassLoaderBase的下面两个方法;

 /*** Load the class with the specified name, searching using the following* algorithm until it finds and returns the class.  If the class cannot* be found, returns <code>ClassNotFoundException</code>.* <ul>* <li>Call <code>findLoadedClass(String)</code> to check if the*     class has already been loaded.  If it has, the same*     <code>Class</code> object is returned.</li>* <li>If the <code>delegate</code> property is set to <code>true</code>,*     call the <code>loadClass()</code> method of the parent class*     loader, if any.</li>* <li>Call <code>findClass()</code> to find this class in our locally*     defined repositories.</li>* <li>Call the <code>loadClass()</code> method of our parent*     class loader, if any.</li>* </ul>* If the class was found using the above steps, and the* <code>resolve</code> flag is <code>true</code>, this method will then* call <code>resolveClass(Class)</code> on the resulting Class object.** @param name The binary name of the class to be loaded* @param resolve If <code>true</code> then resolve the class** @exception ClassNotFoundException if the class was not found*/@Overridepublic Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {if (log.isDebugEnabled())log.debug("loadClass(" + name + ", " + resolve + ")");Class<?> clazz = null;// Log access to stopped class loadercheckStateForClassLoading(name);// (0) Check our previously loaded local class cache 本地缓存clazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Returning class from cache");if (resolve)resolveClass(clazz);//链接classloader  todoreturn clazz;}// (0.1) Check our previously loaded class cacheclazz = findLoadedClass(name);//校验jvm 的appclassloader的缓存中是否存在if (clazz != null) {if (log.isDebugEnabled())log.debug("  Returning class from cache");if (resolve)resolveClass(clazz);return clazz;}// (0.2) Try loading the class with the system class loader, to prevent//       the webapp from overriding Java SE classes. This implements//       SRV.10.7.2String resourceName = binaryNameToPath(name, false);ClassLoader javaseLoader = getJavaseClassLoader();//系统的bootstrap classloader 的classloaderboolean tryLoadingFromJavaseLoader;try {// Use getResource as it won't trigger an expensive// ClassNotFoundException if the resource is not available from// the Java SE class loader. However (see// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for// details) when running under a security manager in rare cases// this call may trigger a ClassCircularityError.// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for// details of how this may trigger a StackOverflowError// Given these reported errors, catch Throwable to ensure any// other edge cases are also caughtURL url;if (securityManager != null) {PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);url = AccessController.doPrivileged(dp);} else {url = javaseLoader.getResource(resourceName);}tryLoadingFromJavaseLoader = (url != null);} catch (Throwable t) {// Swallow all exceptions apart from those that must be re-thrownExceptionUtils.handleThrowable(t);// The getResource() trick won't work for this class. We have to// try loading it directly and accept that we might get a// ClassNotFoundException.tryLoadingFromJavaseLoader = true;}if (tryLoadingFromJavaseLoader) {try {//利用javaser的加载方式 ,即双亲委派的模式,核心类库clazz = javaseLoader.loadClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}}// (0.5) Permission to access this class when using a SecurityManagerif (securityManager != null) {int i = name.lastIndexOf('.');if (i >= 0) {try {securityManager.checkPackageAccess(name.substring(0,i));} catch (SecurityException se) {String error = "Security Violation, attempt to use " +"Restricted Class: " + name;log.info(error, se);throw new ClassNotFoundException(error, se);}}}//是否使用委托方式,即利用父类加载器加载该类的方式boolean delegateLoad = delegate || filter(name, true);// (1) Delegate to our parent if requestedif (delegateLoad) {if (log.isDebugEnabled())log.debug("  Delegating to parent classloader1 " + parent);try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Loading class from parent");if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}}// (2) Search local repositoriesif (log.isDebugEnabled())log.debug("  Searching local repositories");try {//不使用父类加载器的方式,直接重写findclass,clazz = findClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Loading class from local repository");if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}// (3) Delegate to parent unconditionallyif (!delegateLoad) {if (log.isDebugEnabled())log.debug("  Delegating to parent classloader at end: " + parent);try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Loading class from parent");if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}}}throw new ClassNotFoundException(name);}protected void checkStateForClassLoading(String className) throws ClassNotFoundException {// It is not permitted to load new classes once the web application has// been stopped.try {checkStateForResourceLoading(className);} catch (IllegalStateException ise) {throw new ClassNotFoundException(ise.getMessage(), ise);}}
 @Overridepublic Class<?> findClass(String name) throws ClassNotFoundException {if (log.isDebugEnabled())log.debug("    findClass(" + name + ")");checkStateForClassLoading(name);// (1) Permission to define this class when using a SecurityManagerif (securityManager != null) {int i = name.lastIndexOf('.');if (i >= 0) {try {if (log.isTraceEnabled())log.trace("      securityManager.checkPackageDefinition");securityManager.checkPackageDefinition(name.substring(0,i));} catch (Exception se) {if (log.isTraceEnabled())log.trace("      -->Exception-->ClassNotFoundException", se);throw new ClassNotFoundException(name, se);}}}// Ask our superclass to locate this class, if possible// (throws ClassNotFoundException if it is not found)Class<?> clazz = null;try {if (log.isTraceEnabled())log.trace("      findClassInternal(" + name + ")");try {if (securityManager != null) {PrivilegedAction<Class<?>> dp =new PrivilegedFindClassByName(name);clazz = AccessController.doPrivileged(dp);} else {//从本地的具体类名查找 内部方式 ;//webapps libclazz = findClassInternal(name);}} catch(AccessControlException ace) {log.warn("WebappClassLoader.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace("      -->RuntimeException Rethrown", e);throw e;}if ((clazz == null) && hasExternalRepositories) {try {//调用父类的加载方式clazz = super.findClass(name);} catch(AccessControlException ace) {log.warn("WebappClassLoader.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace("      -->RuntimeException Rethrown", e);throw e;}}if (clazz == null) {if (log.isDebugEnabled())log.debug("    --> Returning ClassNotFoundException");throw new ClassNotFoundException(name);}} catch (ClassNotFoundException e) {if (log.isTraceEnabled())log.trace("    --> Passing on ClassNotFoundException");throw e;}// Return the class we have locatedif (log.isTraceEnabled())log.debug("      Returning class " + clazz);if (log.isTraceEnabled()) {ClassLoader cl;if (Globals.IS_SECURITY_ENABLED){cl = AccessController.doPrivileged(new PrivilegedGetClassLoader(clazz));} else {//如果父类再加载不到的化,cl = clazz.getClassLoader();}log.debug("      Loaded by " + cl.toString());}return clazz;}

Web应用默认的类加载顺序是(打破了双亲委派规则):

  1. 先从JVM的BootStrapClassLoader中加载。
  2. 加载Web应用下/WEB-INF/classes中的类。
  3. 加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。
  4. 加载上面定义的System路径下面的类。
  5. 加载上面定义的Common路径下面的类。

如果在配置文件中配置了``,那么就是遵循双亲委派规则,加载顺序如下:

  1. 先从JVM的BootStrapClassLoader中加载。
  2. 加载上面定义的System路径下面的类。
  3. 加载上面定义的Common路径下面的类。
  4. 加载Web应用下/WEB-INF/classes中的类。
  5. 加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。

Tomcat对用户类库与类加载器的规划

 在其目录结构下有三组目录(“/common/”、“/server/”、“/shared/”)可以存放Java类库,另外还可以加上Web应用程序本身的目录“/WEB-INF/”,一共4组,把Java类库放置在这些目录中的含义分别如下:

1)放置在/commom目录中:类库可被Tomcat和所有的Web应用程序共同使用

2)放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见

3)放置在/shared目录中:类库可被所有的Web应用程序所共同使用,但对Tomcat自己不可见

4)放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见

为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,所下图:

    

最上面的三个类加载器是JDK默认提供的类加载器,这三个加载器的的作用之前也说过,这里不再赘述了,可以在http://www.cnblogs.com/ghoster/p/7594224.html简单了解。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebAppClassLoader则是Tomcat自己定义的类加载器,他们分别加载/common/、/server/、/shared/和/WebApp/WEB-INF/中的Java类库。其中WebApp类加载器和jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个jsp文件对应一个Jsp类加载器

    从上图的委派关系可以看出,CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的哪一个Class,它出现的目的就是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过在建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能

OSGi:tomcat是如何打破双亲委派的

  既然说到OSGI,就要来解释一下OSGi是什么,以及它的作用

  OSGi(Open Service Gateway Initiative):是OSGi联盟指定的一个基于Java语言的动态模块化规范,这个规范最初是由Sun、IBM、爱立信等公司联合发起,目的是使服务提供商通过住宅网管为各种家用智能设备提供各种服务,后来这个规范在Java的其他技术领域也有不错的发展,现在已经成为Java世界中的“事实上”的模块化标准,并且已经有了Equinox、Felix等成熟的实现。OSGi在Java程序员中最著名的应用案例就是Eclipse IDE

  OSGi中的每一个模块(称为Bundle)与普通的Java类库区别并不大,两者一般都以JAR格式进行封装,并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它所依赖的Java Package(通过Import-Package描述),也可以声明他允许导出发布的Java Package(通过Export-Package描述)。在OSGi里面,Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖(至少外观上如此),而且类库的可见性能得到精确的控制,一个模块里只有被Export过的Package才可能由外界访问,其他的Package和Class将会隐藏起来。除了更精确的模块划分和可见性控制外,引入OSGi的另外一个重要理由是,基于OSGi的程序很可能可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启动程序的其中一部分,这对企业级程序开发来说是一个非常有诱惑性的特性

  OSGi之所以能有上述“诱人”的特点,要归功于它灵活的类加载器架构。OSGi的Bundle类加载器之间只有规则,没有固定的委派关系。例如,某个Bundle声明了一个它依赖的Package,如果有其他的Bundle声明发布了这个Package,那么所有对这个Package的类加载动作都会为派给发布他的Bundle类加载器去完成。不涉及某个具体的Package时,各个Bundle加载器是平级关系,只有具体使用某个Package和Class的时候,才会根据Package导入导出定义来构造Bundle间的委派和依赖

  另外,一个Bundle类加载器为其他Bundle提供服务时,会根据Export-Package列表严格控制访问范围。如果一个类存在于Bundle的类库中但是没有被Export,那么这个Bundle的类加载器能找到这个类,但不会提供给其他Bundle使用,而且OSGi平台也不会把其他Bundle的类加载请求分配给这个Bundle来处理

  一个例子:假设存在BundleA、BundleB、BundleC三个模块,并且这三个Bundle定义的依赖关系如下:

  BundleA:声明发布了packageA,依赖了java.*的包

  BundleB:声明依赖了packageA和packageC,同时也依赖了Java.*的包

  BundleC:声明发布了packageC,依赖了packageA

  那么,这三个Bundle之间的类加载器及父类加载器之间的关系如下图:

  

  由于没有涉及到具体的OSGi实现,所以上图中的类加载器没有指明具体的加载器实现,只是一个体现了加载器之间关系的概念模型,并且只是体现了OSGi中最简单的加载器委派关系。一般来说,在OSGi中,加载一个类可能发生的查找行为和委派关系会比上图中显示的复杂,类加载时的查找规则如下:

    1)以java.*开头的类,委派给父类加载器加载

    2)否则,委派列表名单内的类,委派给父类加载器加载

    3)否则,Import列表中的类,委派给Export这个类的Bundle的类加载器加载

    4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载

    5)否则,查找是否在自己的Fragment Bundle中,如果是,则委派给Fragment bundle的类加载器加载

    6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载

    7)否则,查找失败

  从之前的图可以看出,在OSGi里面,加载器的关系不再是双亲委派模型的树形架构,而是已经进一步发展成了一种更复杂的、运行时才能确定的网状结构。

相关面试题:一个类的静态块是否可能被执行两次

一个自于网易面试官的一个问题,一个类的静态块是否可能被执行两次。

答案,如果一个类,被两个 osgi的bundle加载, 然后又有实例被初始化,其静态块会被执行两次

转自:面试必备:什么时候要打破双亲委派机制?什么是双亲委派? (图解+秒懂+史上最全) - 疯狂创客圈 - 博客园

【有料】面试必备:什么时候要打破双亲委派机制?什么是双亲委派? (图解+秒懂+史上最全)相关推荐

  1. 面试必备:什么时候要打破双亲委派机制?什么是双亲委派? (图解+秒懂+史上最全)

    文章很长,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 ...

  2. redis cluster 集群 HA 原理和实操(史上最全、面试必备)

    文章很长,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 ...

  3. 2023史上最全Java面试题【完整版】跳槽必备,看完轻松收撕面试官

    ✨作者简介:杨 戬,博客专家.github开源作者 ✨多年工作总结:Java学习路线总结,小白逆袭Java技术总监 ✨技术交流:定期更新Java硬核干货,不定期送书活动.助你实现技术飞跃 ✨关注公众号 ...

  4. 史上最全Redis面试49题(含答案):哨兵+复制+事务+集群+持久化等

    最全面试题答案系列 史上最强多线程面试44题和答案:线程锁+线程池+线程同步等 最全MySQL面试60题和答案 史上最全memcached面试26题和答案 史上最全Spring面试71题与答案 今天主 ...

  5. 【必备】史上最全的浏览器 CSS JS Hack 手册

    浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本("杰出代表"是 IE)也有差异.因此,浏览器兼容成为前端开发人员的必备技能.如果有一份浏览器 Hack 手册,那查询起来就方 ...

  6. mac电脑macOS常用软件必备软件命令行安装,史上最全mac插件2

    mac电脑macOS常用软件必备软件命令行安装,史上最全mac插件1见博客链接 mac电脑macOS常用软件必备软件命令行安装,史上最全mac插件  是我的第一篇文章,非常全,不敢轻易编辑.你懂的.. ...

  7. 玩转Python必备:史上最全的Python库,【值得收藏,事半功倍】

    库名称     简介 Chardet字符编码探测器,可以自动检测文本.网页.xml的编码. colorama主要用来给文本添加各种颜色,并且非常简单易用. Prettytable主要用于在终端或浏览器 ...

  8. 2019史上最全java面试题题库大全800题含答案(面试宝典)

    2019史上最全java面试题题库大全800题含答案(面试宝典) 1. meta标签的作用是什么 2. ReenTrantLock可重入锁(和synchronized的区别)总结 3. Spring中 ...

  9. 面试题(3)史上最全阿里技术面试题目

    面试题(1)最全BAT面试精华汇总 面试题(2)阿里最全面试100题 面试题(3)史上最全阿里技术面试题目 面试题(4)阿里等BAT必考多线程面试60题 面试题(5)BAT面试笔试33题:JavaLi ...

最新文章

  1. Nature综述:微生物构成的氮循环网络(收藏)
  2. 多个常见代码设计缺陷
  3. 建站手册-语义网:语义网
  4. [渝粤教育] 西南科技大学 西方经济学 在线考试复习资料
  5. phpcms v9 数据源
  6. DelayedFetch分析
  7. Java之final详解
  8. 创建ServerSocket出错Permission denied
  9. openoffice 64_科学网—四棱的核桃:卡片机傻拍2020(64)
  10. Springboot--Ehcache-Jpa (1)
  11. 有效id和密码_ID和密码恢复
  12. RestTemplate application/octet-stream处理
  13. PPPOE和IPOE
  14. 【计算机网络】知识点整理 第四章 网络层(王道考研视频学习记录)
  15. 灰色关联分析过程及代码实现
  16. 网络能看到计算机 但是进不去,共享文件夹 在网络邻居看到别人的电脑 进不去...
  17. layui引入第三方依赖
  18. 【第三期】电子元器件创意作品,附带高清原图,共计60张
  19. DBeaver(数据库管理软件) v22.0.1 使用安装教程
  20. 股票的区分: 什么是 A股,B股,H股,N股?

热门文章

  1. 在线URLEncode/URLDecode网址
  2. Oracle数据库Timestamp数据差值计算Sql语句
  3. ES5 ES6相关内容 day15
  4. 关于ES5和ES6的简介
  5. Keystone的安装及其配置
  6. 题解:女神间的 BOYI # 博弈论 # surreal number
  7. MDCC王戈点爆全场:Smule乐器王是怎样炼成的
  8. 计算机毕业设计SSM本科培训班学员信息管理系统【附源码数据库】
  9. PTA题目 到底是不是太胖了
  10. 速卖通关键词挖掘工具_利用SEO工具挖掘同行竞争对手关键词数据快速布局网站词库...