在介绍ClassLoader之前,先提几个问题:
1、新建一个java工程,创建一个Long类,在里面写上如下代码

package java.lang;public class Long {    public static void main(String[] args) {        System.out.print("long");    }}

2、运行,会发生什么呢?

一、常见的ClassLoader介绍

classLoader 其实就是类加载器,具体作用就是将类加载到java虚拟机中去,之后类中的程序就可以运行了。java中常见的类加载器主要有以下三个:1、BootstrapClassLoader(根类加载器) : BootstrapClassLoader是纯C++实现的类加载器,没有对应的java类,主要加载的是jre/lib/目录下的核心库2、ExtClassLoader(扩展类加载器) : 类的全名sun.misc.Launcher

3、AppClassLoader(应用类加载器) : 类的全名sun.misc.LauncherExtClassLoader,主要加载的是jre/lib/ext目录下的扩展包<br>∗∗∗3、AppClassLoader(应用类加载器):∗∗∗类的全名sun.misc.LauncherAppClassLoader,主要加载CLASSPATH路径下的包

二、下面通过程序,打印出上面3个类加载器的加载路径

1、AppClassLoader(应用类加载器)
public class Main {

    public static void main(String[] args) {        Class mainClass = Main.class;            ClassLoader classLoader = mainClass.getClassLoader();//打印类加载器名称            System.out.print(classLoader.toString());//打印AppClassLoader加载路径            URL[] urLs = ((URLClassLoader) classLoader).getURLs();for (URL url: urLs) {                System.out.print(url);                System.out.print("\n");            }    }}打印结果sun.misc.Launcher$AppClassLoader@4e0e2f2afile:/D:/Study_Space/ClassLoaderDemo/ClassLoaderDemo/bin/

从打印结果可看到,AppClassLoader主要加载CLASSPATH路径下类,还有一些第三方包等,也就是加载我们应用程序的类和第三方库

2、ExtClassLoader(扩展类加载器)

提到ExtClassLoader,这里必须注意一下两个概念的区别:父类加载器和类中继承,父类加载器不像继承,它是没有父子关系的,看一张ClassLoader类的继承关系图

在这里插入图片描述

可以看到,AppClassLoader的父类是URLClassLoader,但是AppClassLoader的父类加载器是ExtClassLoader,看下面代码输出结果

public class Main {

    public static void main(String[] args) {

        Class mainClass = Main.class;            ClassLoader classLoader = mainClass.getClassLoader();//获取AppClassLoader的父类加载器            ClassLoader extClassLoader = classLoader.getParent();//打印类加载器名称            System.out.print( extClassLoader.toString());            System.out.print("\n");            URL[] extUrLs = ((URLClassLoader) extClassLoader).getURLs();//打印AppClassLoader加载路径for (URL url: extUrLs) {                System.out.print(url);                System.out.print("\n");            }    }}打印结果sun.misc.Launcher$ExtClassLoader@2a139a55file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/access-bridge-64.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/cldrdata.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/dnsns.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/jaccess.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/jfxrt.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/localedata.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/nashorn.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunec.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunjce_provider.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunmscapi.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/sunpkcs11.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/ext/zipfs.jar

可以看出,AppClassLoader的父类加载器是ExtClassLoader,而ExtClassLoader主要加载的是jre/lib/ext目录下的扩展包。

3、BootstrapClassLoader(根类加载器)

也称之为启动类加载器,看到这里,是不是已经想到BootstrapClassLoader是ExtClassLoader的父类加载器呢。答案是错误的,因为BootstrapClassLoader是用C++实现的,他没有对应的java类,所以BootstrapClassLoader不是ExtClassLoader的父类加载器。而且因为使用C++实现的,所以想要获取它的加载路径,就必须用特殊的手段去获取:通过看AppClassLoader和ExtClassLoader源码(后面会有源码解析)我们知道,他们都是Launcher的内部类,而Launcher提供了一个方法(getBootstrapClassPath),来获取BootstrapClassLoader的加载路径,所以用什么手段来获取BootstrapClassLoader的加载路径,就呼之欲出了。

public class Main {

    public static void main(String[] args) {

            try {                Class launcherClass = Class.forName("sun.misc.Launcher");                Method methodGetBootstrapClassPath= launcherClass.getDeclaredMethod("getBootstrapClassPath", null);                if(methodGetBootstrapClassPath!=null){                    methodGetBootstrapClassPath.setAccessible(true);                    Object obj = methodGetBootstrapClassPath.invoke(null, null);                    if(obj!=null){                        Method methodGetUrls =  obj.getClass().getDeclaredMethod("getURLs", null);                        if(methodGetUrls != null){                            methodGetUrls.setAccessible(true);                             URL[] bootstrapClassPath = (URL[]) methodGetUrls.invoke(obj, null);                             for (URL url: bootstrapClassPath) {                                    System.out.print(url);                                    System.out.print("\n");                                }                        }                    }                }

            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {                e.printStackTrace();            }

    }}

打印结果file:/C:/MySoft/Develop_File/jdk1.8/jre/lib/resources.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/rt.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/sunrsasign.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/jsse.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/jce.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/charsets.jarfile:/C:/MySoft/Develop_File/jdk1.8/jre/lib/jfr.jar...

可以看出,BootstrapClassLoader负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。

上面就是java中三个类加载器的加载路径,下面将会讲解一下类加载器是怎么加载一个类的

二、类加载器是怎么加载一个类的(父委托加载机制)

类加载器主要通过loadClass这个方法去加载一个类的,下面看源码

public Class> loadClass(String name) throws ClassNotFoundException {        return loadClass(name, false);    }

protected Class> loadClass(String name, boolean resolve) 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);//调用父类加载器的loadClass                    } else {//父类加载器为空,就调用findBootstrapClass方法                        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;        }    }

    private Class> findBootstrapClassOrNull(String name) {        if (!checkName(name)) return null;        return findBootstrapClass(name);    }

    // return null if not found    private native Class> findBootstrapClass(String name);

通过上面源码我们可以知道,如果当类加载器是AppClassLoader时,会判断它的父加载器parent是否为空,如果不为空,则调用父加载器也就是ExtClassLoader的loadClass(name, false)方法,这个时候又会判断一次ExtClassLoader的父加载器是否为空,如果不为空,则调用ExtClassLoader的父加载器loadClass方法,当然ExtClassLoader父加载器是为空的,所以,最终会调用BootstrapClassLoader类加载器去加载一个类,这一套加载机制,叫做 父委托加载机制
来一张图,方便理解

三、父委托加载机制的好处

现在咱们回头看看最开始那个问题,你会发现回报一个错误

错误: 在类 java.lang.Long 中找不到 main 方法, 请将 main 方法定义为:   public static void main(String[] args)否则 JavaFX 应用程序类必须扩展javafx.application.Application

现在应该明白,为什么会报一个这样的错误了把。就是因为父委托类加载机制。那父委托加载机制有什么好处呢?
这样可以提高软件系统的安全性,防止JVM核心类被覆盖。如果,别人自己写了一个Long类,在Long类里面添加一些恶意代码,所以很有可能会有人利用这个漏洞去攻击你的程序。同时,他也可以防止类被重复加载。

四、如何自定义类加载器

自定义类加载器分为两步:

  • 继承 java.lang.ClassLoader

  • 重写父类的 findClass 方法。
    可能这里会有疑问,为什么只重写findClass方法?不是说只能重写findClass 方法,你也可以重写 loadClass 方法,但是重写loadClass 就得写 ClassLoader 搜索类的逻辑,当在 loadClass 中找不到相关类, loadClass 就会调用 findClass 来搜索类。这个逻辑ClassLoader 已经实现了,所以一般情况下没必要再去重写。只要重写一个 findClass 就够了。

    自定义类加载的原因:

  • 代码安全:比如加密编译后的字节码文件,然后通过自定义类加载器去加载。这样就可以减少被反编译的危险。

  • 动态加载:比如根据需求,动态的从网络或本地加载指定的字节码文件。

    五、被打破的父委托机制

    在实际的运用中,父委托机制 很好的解决了 基础类 统一加载的问题,如上所述的 Long 类,这是我们在调用jdk中的一些基础类;但是有的时候,jdk中的基础类需要调用我们用户的代码,那该怎么办?典型的例子就是JNDI服务

JNDI服务,它的代码由 启动类加载器(BootstrapClassLoader) 去加载(在JDK1.3时放进rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器主要加载的是jre/lib/ 目录下的核心库,无法加载这些独立厂商所编写的代码,该怎么办?

为了解决上面的问题,java团队设计了 线程上下文件类加载器(Thread Context ClassLoader)

1、线程上下文件类加载器(Thread Context ClassLoader)
  • 这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;

  • 如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。

有了线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。

Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

参考文章:https://www.sohu.com/a/334000357_505800https://blog.51cto.com/14888355/2515924

在这里插入图片描述

打破双亲委派机制有什么用_被打破的双亲委托机制相关推荐

  1. Java双亲委派模型是什么、优势在哪、双亲委派模型的破坏

    定义 双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终 ...

  2. 【JVM】Java类的加载流程以及双亲委派,全盘托管,以及如何打破双亲委派机制

    JVM基础生命周期流程图 只有main()方法的java程序执行流程 classLoader.loadClass()的类加载流程(除引导类,所有类都一样) 加载:通过IO查找读取磁盘上的字节码文件,在 ...

  3. java 父委托机制优点_类加载器及父亲委托机制

    一.类加载器 1.在Java中,有两种类型的类加载器,分别是JVM自带的类加载器和用户自定义的类加载器. 2.JVM自带的类加载器有三种,如下: 根(Bootstrap)类加载器:该加载器没有父加载器 ...

  4. 轻松掌握mysql数据库锁机制的相关原理_轻松掌握MySQL数据库锁机制的相关原理...

    不同于行级或页级锁定的选项: · 版本(例如,为并行的插入在MySQL中使用的技术),其中可以一个写操作,同时有许多读取操作.这明数据库或表支持数据依赖的不同视图,取决于访问何时开始.其它共同的术语是 ...

  5. 打破双亲委派机制有什么用_你确定你真的理解双亲委派了吗?!

    最近一段时间,我在面试的过程中,很喜欢问双亲委派的一些问题,因为我发现这个问题真的可以帮助我全方位的了解一个候选人. 记得前几天一次面试过程中,我和一位候选人聊到了JVM的类加载机制的问题,他谈到了双 ...

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

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

  7. JVM(1)之JVM的组成详解(字符串常量池+双亲委派机制+JIT即时编译......)

    以下总结自:<深入理解java虚拟机> + 宋红康老师视频 字节码文件介绍:深入理解JVM之Java字节码(.class)文件详解_Windy_729的博客-CSDN博客_字节码文件 JV ...

  8. Java双亲委派模型:为什么要双亲委派?如何打破它?破在哪里?

    文章目录 一.前言 二.类加载器 三.双亲委派机制 1.什么是双亲委派 2.为什么要双亲委派? 四.破坏双亲委派 1.直接自定义类加载器加载 2.跳过AppClassLoader和ExtClassLo ...

  9. JVM系列(三):打破双亲委派及案例

    打破双亲委派机制 上一章我们讲到了类加载器和双亲委派机制的一些原理,对于双亲委派机制,我们也了解了双亲委派机制有沙箱安全机制和避免类的重复加载两大优点,这一章我们来讲述为什么要打破双亲委派机制以及如何 ...

最新文章

  1. python怎样判断一个文件是否存在_python如何判断一个文件是否存在
  2. 多才多艺的移动式人形机器人iPal,担当起小朋友的“好家教”
  3. 腾讯的前端工程师,是如何精进技术的?
  4. PyTorch nn.Module 一些疑问
  5. .net core ef mysql 的使用
  6. 转:Windows Phone 7 设计简介
  7. java 日志乱码_【开发者成长】JAVA 线上故障排查完整套路!
  8. 研究机构:宁德时代是上半年全球第二大EV与PHEV电池供应商
  9. Spring @Autowird
  10. Django工作笔记001---Django简介
  11. “进化”的搜索方式:揭秘微软语义搜索背后的技术
  12. .Net中的加密解密
  13. mysql如何输出一句话_MySQL/ACCESS导出一句话拿WebShell后门命令
  14. Install Toad for Oracle 10.6 on Winows 7 X64
  15. android word 转pdf插件下载,word转pdf转换器
  16. 【matlab深度学习工具箱】classificationLayer参数详解
  17. 七牛云智能日志管理平台的应用与设计
  18. U盘插入后只显示安全删除硬件问题
  19. 【MySQL系列】数据结构详解(全网最全)
  20. 计算机博士要几篇顶会论文,我,斯坦福读博,5年5篇顶会论文,却依然觉得研究生涯充满挫折...

热门文章

  1. linux awk 日志分析,Linux Awk使用案例总结 nginx日志统计
  2. 作者:张思思(1985-),女,博士,中国科学院北京基因组研究所生命与健康大数据中心工程师...
  3. 【2016年第5期】基于征信大数据分析的中国劳动力人口迁徙研究
  4. 【2016年第1期】农业大数据给商品交易所带来的机遇和挑战
  5. 《大数据》2015年第3期“网络大数据专题”——基于特征学习的文本大数据内容理解及其发展趋势...
  6. 【Python】Smtplib正确模拟发送QQ邮件
  7. 【Python】处理 pydotplus.graphviz.InvocationException: GraphViz’s executables not found
  8. 【数据结构与算法】二叉树基本算法锦集
  9. 简洁版即时聊天---I/O多路复用使用
  10. React Suspense提供Redux的替代方案