双亲委派

提起 java 类加载器,自然绕不开其双亲委派模型

什么是双亲委派

提起双亲委派,首先想到便是那张经典的向上委派图

一般场景下,当某个类将要被加载时,由系统上下文默认的类加载器Thread.currentThread().getContextClassLoader()对该类进行加载,通常这个类加载器为AppClassLoaderAppClassLoader不会直接尝试加载这个类,而是委托给它的父类ExtClassLoader对其加载,ExtClassLoader则会直接委托父类BootstrapClassLoader加载,当BootstrapClassLoader找不到要加载的目标类时,才会逐级下放给子类,否则类加载完毕

这里需要明确2个概念:

  • 父类加载器 这里虽然称为父加载器,但却跟 java 的继承没有关系,反而这里实现“父”加载器是通过组合的方式,本文后续还会说明
  • 类定义加载器 真正加载该类的类加载器,而不是发起加载动作的类加载器;比如AppClassLoader发起加载String类,而真正执行加载动作的却是BootstrapClassLoader,所以定义String的类加载器为BootstrapClassLoader

双亲委派好处

java 的设计者们为什么要大费周章设计如此复杂的加载模型呢?其实主要有以下两个好处

  • 避免同一个类被重复加载

    当用户定义了相当多的类加载器时,如果没有此委托模型,将产生大量的类重复加载,占用内存(同一个类被不同的类加载器加载时,将会产生多个副本),可参见下面用例

  • 保护 jdk 核心类不被篡改

    java向来以安全著称,试想,如果没有此隔离机制,某个依赖的 jar 包中被恶意篡改、注入java.lang.String类后,将导致整个进程的奔溃;同样参见上图,由于双亲委派机制,java 内部类不管由自定义类加载器还是AppClassLoader,都仅会产生一份实例,且一定由BootstrapClassLoader加载

jdk 类加载器剖析

下面我们依次分析3个类加载器

  • AppClassLoader

    • 路径sun.misc.Launcher#AppClassLoader

    • 此类加载器主要负责加载用户编写的java文件,同时java默认的线程上下文类加载器亦为此类。我们翻看sun.misc.Launcher源码验证此观点

    • 类结构图

    • 总结

      在一般的工程中,此类用来加载初jdk内部类外的所有java文件,如果项目工程需要自定义类加载器的话,一般也是会继承自此类来保证双亲委派模型;不过有些做三方包、二方包隔离的中间件会自定义多个类加载器继承自ExtClassLoader,从而保证用户编写的代码、依赖的jar包做到真正隔离

  • ExtClassLoader
    • 路径sun.misc.Launcher#ExtClassLoader

    • 类结构图

    • 解析

      翻看源码,发现此类的parent为null

      而加载class的方法ClassLoader#loadClass定义如下

      即某个类加载器的parent指定为 null 时,它的父类会自动变为BootstrapClassLoader,而BootstrapClassLoader定义在native方法上

    • 加载哪些类?

      ExtClassLoader会加载系统变量java.ext.dirs对应的所有路径中的 jar 包,以macos为例,System.getProperty("java.ext.dirs") 内容如下:

      /Users/***/Library/Java/Extensions
      /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext
      /Library/Java/Extensions
      /Network/Library/Java/Extensions
      /System/Library/Java/Extensions
      /usr/lib/java
  • BootstrapClassLoader
    • 加载哪些类?

      BootstrapClassLoader加载java的核心类库,我们可以通过sun.misc.Launcher.getBootstrapClassPath().getURLs()来获取其具体路径,以macos为例,内容如下:

      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/resources.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/sunrsasign.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jsse.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jce.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/charsets.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jfr.jar
      file:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/classes

至此,我们可以把双亲委派的模型画的更具体一些

打破双亲委派

java 双亲委派模型似乎固若金汤,足够安全;不过我们自定义的类加载器想打破这个委派机制的话,也是相当容易,甚至 jdk 自身在处理某些场景时,自己也会打破该机制,比如赫赫有名的 SPI

如何打破

如何打破双亲委派其实非常简单,我们先看 java 是如何保证双亲委派正常运行的

ClassLoaderloadClass方法保证了双亲委派,不过此方法是被protected修饰的,所以任何子类都可以重写此方法;java 建议使用者重写findClass方法,这样可以在不破坏双亲委派的机制下,达到加载自身类的目的。不过在某些场景下,我们依旧可以直接重写loadClass来破坏委派机制

SPI

讨论 java 类加载器为何要提到spi ? 是的,讨论类加载器一定绕不开 spi 的设计理念,spi 对委派概念是一次冲击,我们也可以看到 java 设计者们在双亲委派问题上做的让步

什么是spi

我们先简单回顾下 spi 的定义;spi 是 Service Provider Interface 的简写,翻译成中文就是服务提供发现接口。

要使用Java SPI,需要遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  • 2、接口实现类所在的jar包放在主程序的classpath中;
  • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • 4、SPI的实现类必须携带一个不带参数的构造方法;

java 通过 spi 机制,在解耦的同时实现了可插拔操作

带来的问题

通过上文分析,我们知道像诸如rt.java等核心类库均交由BootstrapClassLoader加载,不过核心类库返回的对象却是用户自定义的实现类,这在双亲委派机制上是说不通的,下面我们拿jdbc驱动举例说明

首先我们看下驱动启动的代码实例

Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/wolong_extend?rewriteBatchedStatements=true&useServerPrepStmts=true","root","root");
System.out.println(connection.getClass().getName());

最后的输出结果为 com.mysql.jdbc.JDBC4Connection

mysql 对应的实现类在代码之前并未加载,而在加载 java rt.jar包中的java.sql.DriverManager时却返回了一个用户定义的对象,给人的错觉便是BootstrapClassLoader加载了com.mysql.jdbc.JDBC4Connection,那真实的情况又是什么样呢?

  • 注:当 java 加载类 A,而 A 又依赖了 B,java 会用加载 A 的类加载器去加载类 B

DriverManager剖析

翻看源码发现 java 为了解决 spi 问题引入了上下文类加载器的概念Thread.currentThread().getContextClassLoader(),首先获取调用类caller的类加载器,如果为空的话,便直接获取上下文类加载器

加载顺序

后记

所有实例代码 https://github.com/xijiu/share

Java ClassLoader浅析的更多相关文章

  1. Android(java)学习笔记106-1:深入分析Java ClassLoader原理

    1. 前言: Android中的动态加载机制能更好的优化我们的应用,同时实现动态的更新,这就便于我们管理我们的应用,通过插件化来减轻我们的内存以及CPU消耗,在不发布新版本的情况下能更新某些模块. 当 ...

  2. Java Classloader机制解析(转)

    做Java开发,对于ClassLoader的机制是必须要熟悉的基础知识,本文针对Java ClassLoader的机制做一个简要的总结.因为不同的JVM的实现不同,本文所描述的内容均只限于Hotspo ...

  3. [转载] 深入了解Java ClassLoader、Bytecode 、ASM、cglib

    转载自http://www.iteye.com/topic/98178   一.Java ClassLoader 1,什么是ClassLoader 与 C 或 C++ 编写的程序不同,Java 程序并 ...

  4. Java Classloader机制解析

    做Java开发,对于ClassLoader的机制是必须要熟悉的基础知识,本文针对Java ClassLoader的机制做一个简要的总结.因为不同的JVM的实现不同,本文所描述的内容均只限于Hotspo ...

  5. Java ClassLoader加载机制理解 实际例子

    针对 Java ClassLoader加载机制理解, 做了个如何自定制简单的ClassLoader,并成功加载指定的类. 不废话,直接上代码. package com.chq.study.cl; im ...

  6. Java ClassLoader加载机制理解

    今天看到了一篇介绍Java ClassLoader加载机器的文章, 才发觉一直来自己的肤浅, 好好地给补了一课, 不得不存档! 原文地址: http://www.blogjava.net/lhulcn ...

  7. java classloader原理深究

    前面已经写过一篇关于java classloader的拙文java classloader原理初探. 时隔几年,再看一遍,觉得有些地方显得太过苍白,于是再来一篇: 完成一个Java类之后,经过java ...

  8. Android(java)学习笔记45:深入分析Java ClassLoader原理

    1. 前言: Android中的动态加载机制能更好的优化我们的应用,同时实现动态的更新,这就便于我们管理我们的应用,通过插件化来减轻我们的内存以及CPU消耗,在不发布新版本的情况下能更新某些模块. 当 ...

  9. 转 Java Classloader机制解析

    转 Java Classloader机制解析 发表于11个月前(2014-05-09 11:36)   阅读(693) | 评论(0) 9人收藏此文章, 我要收藏 赞1 慕课网,程序员升职加薪神器,点 ...

随机推荐

  1. PyQt(Python+Qt)学习随笔:纯文本编辑器QPlainTextEdit功能详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.概述 QPlainTextEdit是用于纯文本的一个高级文档编辑器 ...

  2. PyQt(Python+Qt)学习随笔:部件拉伸策略sizePolicy优先级

    部件的尺寸调整策略或拉伸策略sizePolicy有7个值,如果同一个布局中的不同部件设置了不同的拉伸策略策略,在整个布局空间拉伸时,它们会怎么进行拉伸处理呢? 在未设置拉伸因子的情况下,Qt中这些拉伸 ...

  3. flask实现分类搜索的小测试

    最新学长要求实现一个搜索的功能呢,也费了一点功夫.这个案例也没有学长写的好,比学长的实现差了不少,待我仔细研究习再发出相应代码 项目要求,搜索语法如下: titile: xxx #搜索titile的所 ...

  4. 揭秘 VMAF 视频质量评测标准

    作者:杨洋,阿里云技术专家,从事直播相关媒体处理引擎开发 背景 图像质量的衡量是个老问题,对此人们提出过很多简单可行的解决方案.例如均方误差(Mean-squared-error,MSE).峰值信噪比 ...

  5. 第六篇 Scrum 冲刺博客

    一.站立式会议 1. 会议照片 2. 工作汇报 团队成员名称 昨日完成的工作 今天计划完成的工作 工作中遇到的困难 陈锐基 - 完成关注/取关功能以及动态绑定- 完成点赞/取消点赞功能以及动态绑定 - ...

  6. v-if和v-for

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. linux c++ 内存泄漏检测工具:AddressSanitizer(ASan)

    1.介绍 AddressSanitizer(ASan),该工具为gcc自带,4.8以上版本均可以使用. 2.使用 编译的方式很简单,只需要添加 -fsanitize=address -g 即可,如 g ...

  8. 算法——寻找第K个最大的数

    在未排序的数组中找到第 k 个最大的元素. 链接: leetcode. 解题思路:通过快速排序的思想方法,每次随机获取指定范围内一个树的排序位置,然后根据这个位置,再重新指定范围,直到这个位置索引满足 ...

  9. STL——容器(deque) deque 的大小

    1. deque 的大小 deque.size();              //返回容器中元素的个数 1 #include <iostream> 2 #include <dequ ...

  10. Day5 - 07 函数的参数-参数组合

    现在我们学完了位置参数.默认参数.可变参数.关键字参数.命名关键字参数五种参数类型.在Python中定义函数,可以使用这五种参数进行组合.但是参数定义的顺序必须是:必选参数.默认参数.可变参数.命名关 ...