JVM的类加载器和双亲委派模式你了解吗


Posted in Java/Android onMarch 13, 2022

类加载器

Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(ClassLoader)。

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

名称 加载的类 说明
Bootstrap ClassLoader(启动类加载器) JAVA_HOME/jre/lib 无法直接访问
Extension ClassLoader(拓展类加载器) JAVA_HOME/jre/lib/ext 上级为Bootstrap,显示为null
Application ClassLoader(应用程序类加载器) classpath 上级为Extension
自定义类加载器 自定义 上级为Application

1、启动类加载器

可通过在控制台输入指令,使得类被启动类加器加载,它是用C++写的,看不到源码;其他类加载器是用Java写的,说白了就是一些Java类,比如扩展类加载器、应用类加载器。

//查询所有被启动类加载器加载的类
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
    System.out.println(url);
}
//查询到的结果
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/classes

由上可以看出启动类加载的都是jre和jre/lib目录下的核心库,具体路径要看你的jre安装在哪里

2、拓展类加载器

如果classpath和JAVA_HOME/jre/lib/ext 下有同名类,加载时会使用拓展类加载器加载。当应用程序类加载器发现拓展类加载器已将该同名类加载过了,则不会再次加载

URL[] urls = ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for (URL url : urls) {
    System.out.println(url);
}
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/dnsns.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/dns_sd.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/jaccess.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/jfxrt.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/localedata.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/nashorn.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunec.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunmscapi.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files/Java/jre1.8.0_131/lib/ext/zipfs.jar

这些类库具体是什么不重要,只需要知道不同的类库可能是被不同的类加载器加载的。

3、应用程序类加载器

URL[] urls = ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs();
for (URL url : urls) {
    System.out.println(url);
}
file:/{项目工程目录}/bin/

这是当前java工程的bin目录,也就是我们自己的Java代码编译成的class文件所在。

4、双亲委派模式

双亲委派模式,调用类加载器ClassLoader 的 loadClass 方法时,查找类的规则。

loadClass源码

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先查找该类是否已经被该类加载器加载过了
        Class<?> c = findLoadedClass(name);
        //如果没有被加载过
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                //看是否被它的上级加载器加载过了 Extension的上级是Bootstarp,但它显示为null
                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) {
                //如果还是没有找到,先让拓展类加载器调用findClass方法去找到该类,如果还是没找到,就抛出异常
                //然后让应用类加载器去找classpath下找该类
                long 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;
    }
}

有一个描述类加载器加载类过程的术语:双亲委派模型。然而这是一个很有误导性的术语,它应该叫做单亲委派模型(Parent-Delegation Model)。但是没有办法,大家都已经这样叫了。所谓双亲委派,这个亲就是指ClassLoader里的全局变量parent,也就是父加载器。

双亲委派的具体过程如下:

  • 当一个类加载器接收到类加载任务时,先查缓存里有没有,如果没有,将任务委托给它的父加载器去执行。
  • 父加载器也做同样的事情,一层一层往上委托,直到最顶层的启动类加载器为止。
  • 如果启动类加载器没有找到所需加载的类,便将此加载任务退回给下一级类加载器去执行,而下一级的类加载器也做同样的事情。
  • 如果最底层类加载器仍然没有找到所需要的class文件,则抛出异常。
  • 所以是一条线传上再传下,并没有什么“双亲”。

JVM的类加载器和双亲委派模式你了解吗

为什么要双亲委派?

:确保类的全局唯一性。

如果你自己写的一个类与核心类库中的类重名,会发现这个类可以被正常编译,但永远无法被加载运行。因为你写的这个类不会被应用类加载器加载,而是被委托到顶层,被启动类加载器在核心类库中找到了。如果没有双亲委托机制来确保类的全局唯一性,谁都可以编写一个java.lang.Object类放在classpath下,那应用程序就乱套了。

从安全的角度讲,通过双亲委托机制,Java虚拟机总是先从最可信的Java核心API查找类型,可以防止不可信的类假扮被信任的类对系统造成危害。

5、自定义类加载器

如果我们自己去实现一个类加载器,基本上就是继承ClassLoader之后重写findClass方法,且在此方法的最后调包defineClass。

5.1、使用场景

  • 想加载非 classpath 随意路径中的类文件
  • 通过接口来使用实现,希望解耦时,常用在框架设计
  • 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

5.2、步骤

  • 继承ClassLoader父类
  • 要遵从双亲委派机制,重写 findClass 方法
    • 不是重写loadClass方法,否则不会走双亲委派机制
  • 读取类文件的字节码
  • 调用父类的 defineClass 方法来加载类
  • 使用者调用该类加载器的 loadClass 方法
protected Class<?> findClass(final String name) throws ClassNotFoundException {
    // 1、安全检查
    // 2、根据绝对路径把硬盘上class文件读入内存
    byte[] raw = getBytes(name); 
    // 3、将二进制数据转换成class对象
    return defineClass(raw);
}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注三水点靠木的更多内容! 

Java/Android 相关文章推荐
springboot @ConfigurationProperties和@PropertySource的区别
Jun 11 Java/Android
浅谈什么是SpringBoot异常处理自动配置的原理
Jun 21 Java/Android
基于Java的MathML转图片的方法(示例代码)
Jun 23 Java/Android
详解Spring Boot使用系统参数表提升系统的灵活性
Jun 30 Java/Android
gateway与spring-boot-starter-web冲突问题的解决
Jul 16 Java/Android
Java spring单点登录系统
Sep 04 Java/Android
关于MybatisPlus配置双数据库驱动连接数据库问题
Jan 22 Java/Android
JVM之方法返回地址详解
Feb 28 Java/Android
Java中API的使用方法详情
Apr 06 Java/Android
教你在 Java 中实现 Dijkstra 最短路算法的方法
Apr 08 Java/Android
SpringCloud项目如何解决log4j2漏洞
Apr 10 Java/Android
java版 简单三子棋游戏
May 04 Java/Android
Java生成日期时间存入Mysql数据库的实现方法
Mar 03 #Java/Android
Java设计模式之享元模式示例详解
解析探秘fescar分布式事务实现原理
关于ObjectUtils.isEmpty() 和 null 的区别
Feb 28 #Java/Android
java objectUtils 使用可能会出现的问题
Feb 28 #Java/Android
JVM之方法返回地址详解
Feb 28 #Java/Android
解决persistence.xml配置文件修改存放路径的问题
Feb 24 #Java/Android
You might like
php smarty 二级分类代码和模版循环例子
2011/06/16 PHP
php中用加号与用array_merge合并数组的区别深入分析
2013/06/03 PHP
php技术实现加载字体并保存成图片
2015/07/27 PHP
Jquery replace 字符替换实现代码
2010/12/02 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
2013/12/16 Javascript
ListBox实现上移,下移,左移,右移的简单实例
2014/02/13 Javascript
javascript实现网页背景烟花效果的方法
2015/08/06 Javascript
JS+CSS实现仿支付宝菜单选中效果代码
2015/09/25 Javascript
jQuery获取checkboxlist的value值的方法
2015/09/27 Javascript
JavaScript图像延迟加载库Echo.js
2016/04/05 Javascript
利用jquery实现实时更新歌词的方法
2017/01/06 Javascript
JavaScript中数组的各种操作的总结(必看篇)
2017/02/13 Javascript
jQuery插件echarts实现的去掉X轴、Y轴和网格线效果示例【附demo源码下载】
2017/03/04 Javascript
自适应布局meta标签中viewport、content、width、initial-scale、minimum-scale、maximum-scale总结
2017/08/18 Javascript
vue 巧用过渡效果(小结)
2018/09/22 Javascript
js canvas实现橡皮擦效果
2018/12/20 Javascript
vue-cli+axios实现文件上传下载功能(下载接收后台返回文件流)
2019/05/10 Javascript
使用FastCGI部署Python的Django应用的教程
2015/07/22 Python
使用pandas把某一列的字符值转换为数字的实例
2019/01/29 Python
在Python中居然可以定义两个同名通参数的函数
2019/01/31 Python
Python零基础入门学习之输入与输出
2019/04/03 Python
利用PyCharm Profile分析异步爬虫效率详解
2019/05/08 Python
PyQt5 实现字体大小自适应分辨率的方法
2019/06/18 Python
完美解决TensorFlow和Keras大数据量内存溢出的问题
2020/07/03 Python
Python filter过滤器原理及实例应用
2020/08/18 Python
HTML5 Canvas入门学习教程
2016/03/17 HTML / CSS
国际性能运动服装品牌:Dare 2b
2018/07/27 全球购物
馥绿德雅美国官方网站:Rene Furterer头皮护理专家
2019/05/01 全球购物
俄罗斯花园种植材料批发和零售网上商店:Беккер
2019/07/22 全球购物
梅花魂教学反思
2014/04/25 职场文书
公司节能减排方案
2014/05/16 职场文书
家具商场的活动方案
2014/08/16 职场文书
2014年语文教研组工作总结
2014/12/06 职场文书
2015年信息中心工作总结
2015/05/25 职场文书
会计主管竞聘书
2015/09/15 职场文书
比较node.js和Deno
2021/04/27 Javascript