类加载器ClassLoader

1.双亲委派模型

java是根据双亲委派模型的加载类的,当一个类加载器加载类时,会先尝试委托给父类加载器去加载,直到到达启动类加载器顶层若加载不了,则再让子类加载器去加载直到类成功加载,否则抛出异常。

双亲委派模型的好处是可以保证类加载的安全性,无论加载哪个类都会向上委托给BootstrapClassLoader类加载器,BootstrapClassLoader加载不了再尝试自己加载,这样就可以保证用不同的类加载器加载可以得到同一个class对象

2. 双亲委派模型的缺陷

依赖双亲委派模型,子类加载器可以使用父类加载器加载的类,而父类加载器无法使用子类加载器加载的类,这是因为子类加载器可以向上委托让父类加载器加载器,而父类加载器无法向下让子类加载器去加载类。这就导致了双亲委派模型并不是能处理所有类加载的场景,它有自己的局限。

另外类加载器有命名空间的概念,一个类加载器的命名空间由这个类加载器加载的类和其父类加载器加载的类组成,并且同一命名不存在全限定名相同的两个类对象,同一命名空间的类是相互可见的,不同命名空间的类不能相互访问。

接下来针对 子类加载器可以使用父类加载器加载的类,而父类加载器无法使用子类加载器加载的类这句话写个简单的例子来证明

3. 用例

首先我们自定义一个类加载器MyClassLoader,它可以加载path路径下的class文件。为了不破坏双亲委派模型,MyClassLoader类没有重写loadClass方法只重写了findClass方法,当父类加载器无法加载指定类的时候,MyClassLoader会调用findClass方法去尝试加载类

以下的类都定义在package包loader下面

public class MyClassLoader extends ClassLoader {

  private String path;

  MyClassLoader() {
    this.path = Objects.requireNonNull(MyClassLoader.class.getResource("/")).getPath();
  }

  MyClassLoader(String path) {
    this.path = path;
  }

  @Override
  public Class<?> findClass(String name) {
    try {
      byte[] bytes = readBytes(name);
      return defineClass(name, bytes, 0, bytes.length);
    } catch (Exception e) {
      return null;
    }
  }

  private byte[] readBytes(String name) throws IOException {
    String realPath = path + "/" + name.replace(".", "/") + ".class";
    System.out.println("&#x81EA;&#x5B9A;&#x4E49;&#x52A0;&#x8F7D;&#x5668;&#x52A0;&#x8F7D;&#x7C7B;" + name + ", class&#x6587;&#x4EF6;&#x8DEF;&#x5F84;:" + realPath);

    try (FileInputStream fileInputStream = new FileInputStream(realPath);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {

      byte[] bytes = new byte[1024];
      while (true) {
        int len = fileInputStream.read(bytes);
        if (len <= 0 0) { break; } bytearrayoutputstream.write(bytes, , len); return bytearrayoutputstream.tobytearray(); }< code></=>

测试代码,下面各个场景都是一样的,定义了myClassLoader类加载器,可以加载路径/Users/xudong下的class文件。因为MyClassLoader由AppClassLoader加载所有它的父类加载器是AppClassLoader

public class ClassC {

  public static void main(String[] args) throws Exception {
    MyClassLoader myClassLoader = new MyClassLoader("/Users/xudong");

    // &#x8BA9;&#x7C7B;&#x521D;&#x59CB;&#x5316;
    Class<?> classA = Class.forName("loader.ClassA", true, myClassLoader);
    Class<?> classB = Class.forName("loader.ClassB", true, myClassLoader);

    System.out.println("classA's classLoader:" + classA.getClassLoader());
    System.out.println("classB's classLoader:" + classB.getClassLoader());
  }
}

场景一

定义两个类ClassA和ClassB,之后将ClassA.class和ClassB.class复制到/Users/xudong/loader目录下

public class ClassA {

}
public class ClassB {

}

类加载器ClassLoader

类加载器ClassLoader

运行测试代码可以看到ClassA和ClassB都由AppClassLoader加载,虽然一开始由myClassLoader加载但根据双亲委派模型会委托给父类加载器加载,父类加载器加载不了再由子类加载。项目classpath和/Users/xudong下都有class文件但AppClassLoader是myClassLoader的父类加载器,因此先由AppClassLoader加载成功返回。

类加载器ClassLoader

场景二

在场景一的基础上,删除classpath路径下的ClassB.class文件,这样的话只有/Users/xudong下有ClassB.class文件了,接着运行测试代码可以看到ClassB类由myClassLoader加载,因为AppClassLoader加载器在classpath下找不到ClassB.class文件,所以由myClassLoader尝试下加载

类加载器ClassLoader

类加载器ClassLoader

场景三

修改ClassB.java重新编译,接着将classpath下的ClassA.class和ClassB.class复制到/Users/xudong/loader路径下,删除classpath下的ClassB.class

public class ClassB {

  static {
    ClassA classA = new ClassA();
    System.out.println("ClassB &#x9759;&#x6001;&#x4EE3;&#x7801;&#x5757;&#x6267;&#x884C;:" + classA.getClass().getClassLoader());
  }
}

运行测试代码可以看到ClassB由MyClassLoader类加载器加载符合场景二的结果,在类加载的时候进行类初始化执行静态代码块,实例化ClassA对象(肯定需要先加载ClassA类),因为ClassB是MyClassLoader加载的所以ClassA也会用MyClassLoader加载,向上委托,因为classpath存在ClassA.class所以会由AppClassLoader加载器加载,这也说明了 子类加载器可以使用父类加载器加载的类。

类加载器ClassLoader

若把classpath下的ClassA.class也删除呢,那么程序不会报错,只是ClassA类由MyClassLoader加载,因为已经加载了ClassA,所以在ClassB实例化的时候不会再加载ClassA:

类加载器ClassLoader

场景四

修改ClassA.java和ClassB.java文件,重新编译,将ClassA.class和ClassB.class复制到/Users/xudong/loader路径下并将classpath下的ClassB.class文件删除

public class ClassA {

  static {
    ClassB classB = new ClassB();
    System.out.println("ClassA &#x9759;&#x6001;&#x4EE3;&#x7801;&#x5757;&#x6267;&#x884C;:" + classB.getClass().getClassLoader());
  }
}

public class ClassB {

}

执行测试代码报错了找不到ClassB,根据前面的场景我们知道ClassA由AppClassLoader加载,接着运行static静态代码块,由AppClassLoader加载ClassB,但classpath下并没有ClassB.class所以加载失败,在这种情况下需要AppClassLoader的子类MyClassLoader才能加载ClassB,但由于双亲委派模型我们知道不知道向下委托加载,这也说明了 父类加载器并不能使用子类加载器加载的类。ClassB不在AppClassLoader类加载器的命名空间里

类加载器ClassLoader

最后如有写错的地方,欢迎指正~

Original: https://www.cnblogs.com/monianxd/p/16627917.html
Author: 默念x
Title: 类加载器ClassLoader

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/622234/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Qt 保持窗口顶层显示最简单方法

    情景: 当前存在两个窗口或以上,先初始化的窗口会被后初始化的窗口覆盖,从而置于底层, 这时一个最简单的方案就是给需要置于顶层的窗口配置事件过滤器,监听窗口状态,当窗口不属于顶层窗口…

    数据库 2023年6月16日
    0124
  • element-ui el-table 悬停/选中 行样式,鼠标样式

    /* &#x7528;&#x6765;&#x8BBE;&#x7F6E;&#x5F53;&#x524D;&#x9875;&am…

    数据库 2023年6月16日
    081
  • SpringMVC完整学习!!!

    1.楔子 1.1、了解MVC 1.2、MVC框架的主要功能 2.初识SpringMVC 2.1、为什么要学习SpringMVC 2.2、了解SpringMVC 3.入门项目初体验!…

    数据库 2023年6月16日
    086
  • 【黄啊码】linux利用lvs+Keepalived实现负载均衡

    负载均衡:两台(一主一备) LVS + Keepalived+三台HTTP服务器 这是我的第一台HTTP服务器【这里使用的是现成lnmp,然后复制出三台一模一样的】 在每台(HTT…

    数据库 2023年6月16日
    0103
  • [Npoi]Npoi导入Excel, 转为Entity

    Npoi导入Excel其实只要读成DataTable就可以随意操作了, 比如转为Entity… By: 胖纸不争NetCore🐧群: 743336452 核心代码: p…

    数据库 2023年6月9日
    088
  • 生产数据库主键超出限制解决方案

    不说那种建表的时候 设置好主键格式 的 解决方案. 事后诸葛啊. 谁都会 不靠谱方案1改主键表结构. 费时! 主键已经超长了.说明 数据量相当大. 改表结构的时间成本你能等得起吗方…

    数据库 2023年6月14日
    088
  • 线程池系列二:一张动图,彻底懂了execute和submit

    ​我们知道线程池通过execute方法执行提交的Runnable任务,但Runnable只是执行任务,没有返回任何信息。 【线程池原理:线程池原来是个外包公司,打工人我悟了】 若是…

    数据库 2023年6月6日
    0113
  • Java面试题(二)–MySQL

    1 存储引擎 1、简单描述一个Mysql的内部结构? MySQL的基本架构示意图:大体来说,MySQL可以分为 server层和 存储引擎层两部分。 ① server层包括连接器、…

    数据库 2023年6月16日
    085
  • RocksDB线程局部缓存

    在开发过程中,我们经常会遇到并发问题,解决并发问题通常的方法是加锁保护,比如常用的spinlock,mutex或者rwlock,当然也可以采用无锁编程,对实现要求就比较高了。对于任…

    数据库 2023年6月9日
    083
  • mysql安装及主从复制配置

    一、安装 mysql8.0 下载mysql 安装包http://mirrors.sohu.com/mysql/MySQL-8.0/ wget http://mirrors.sohu…

    数据库 2023年5月24日
    064
  • Redis的Java客户端

    Redis 的 Java 客户端 Jedis 优点:以 Redis 命令作为方法名称,学习成本低廉,简单且实用 缺点:Jedis 的实例是线程不安全的,在多线程的环境下需要基于线程…

    数据库 2023年6月16日
    090
  • 贪心算法原理及其应用

    概述 贪心算法应该算是那种”只闻其声不见其人”的算法,我们可能在好多地方都会听到贪心算法这一概念,并且它的算法思想也比较简单就是说算法只保证局部最优,进而达…

    数据库 2023年6月11日
    0148
  • gin 使用pprof 进行性能分析

    开发中发现接口的耗时有点久,需要分析一下,之前也使用过pprof,但没有整理,又重新百度了一下,这次就记一下。 在main 文件中加入 pprof.Register(engine)…

    数据库 2023年6月9日
    089
  • 线上问题检测

    ​ jdk 自带工具 1&#x3001;&#x901A;&#x8FC7;top&#x627E;&#x5230;CPU&#x5360;…

    数据库 2023年6月6日
    095
  • 你知道5分钟法则和10字节法则么?

    如果一条数据每5分钟被访问一次,那么它应该常驻在内存中。类似的,如果想存储只有0和1两个值的标志位,相比于将8个标志位打包为1个字节,将1个标志位单独存储为1个字节是更节约的选择。…

    数据库 2023年6月14日
    0109
  • 2022-8-11 网络编程(网络通信)

    网络协议 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这…

    数据库 2023年6月14日
    0107
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球