类加载器 & 打破双亲委派机制(个人总结)

本文探讨了Java类加载器的工作原理,包括启动类加载器、扩展类加载器和系统类加载器的职责。重点介绍了双亲委派机制,解释了其在确保核心类库安全和优先加载方面的优势,同时也分析了在JDBC和Tomcat中可能出现的问题及如何打破双亲委派。此外,还讨论了自定义类加载器的应用场景和实现方式,并以Tomcat为例说明了自定义加载器在解决多版本类共存和JSP热部署中的作用。
摘要由CSDN通过智能技术生成

    声明: 1. 本文为我的个人复习总结, 并那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
              2. 由于是个人总结, 所以用最精简的话语来写文章
              3. 若有错误不当之处, 请指出

类加载器:

  1. 启动类加载器 加载JAVA_HOME/lib下的核心类
  2. 扩展类加载器 加载JAVA_HOME/lib/ext下的扩展类
  3. 系统类加载器 加载classpath下 我们自己编写的类
  4. 自定义加载器 用户自定义路径

以上加载器的父子关系, 并非指的是extend继承, 而是逻辑上的上下级关系

双亲委派机制:

加载一个类的过程:

系统类加载器遇到一个class, 先看看扩展类加载器里有没有这个同全类名的class

如果父类有,便让父类加载器去加载;如果没有,便继续上抛,直至抛到启动类加载器为止

如果启动类也没有,那就下抛,直至能加载为止

注意: 类加载器间的上下级关系, 和它们各自能加载哪些类

优点:

保证了jdk类库的安全问题,自带的类不会被用户自己编写的同名类进行覆盖
而且促使 核心类优先加载

缺点:

  1. 父类级加载器想要加载一些class,而这个class所处位置只有子类加载器才能加载

    这个问题出现在JDBC中, 启动类加载器加载了JDK自带的DriverManager后, DriverManager用到了MySQL厂商的Driver实现。JVM规定某一类加载器加载A类时发现A用到了B,那么它就得先去加载B。所以启动类加载器就也想加载MySQLDriver, 但这个MySQLDriver实现类不在JAVA_HOME/lib下。所以要打破双亲委派,让父类加载器去使用子类加载器加载原本父类够不到的class文件

  2. 同一个类加载器, (不管是不是同一个类加载器实例) 都只能加载一个全类名相同的class, 不能区分版本
    在这里插入图片描述

    这个问题出现在Tomcat中, Tomcat自定义如上图的多个有上下级关系的自定义加载器。
    Tomcat可以运行多个webapp, 而它们可能需要使用不同版本的jar(class); 当第一个webapp要用WebAppClassLoader想加载jar-version1.0后, 抛给了父类的CommonClassLoader去加载, 于是它加载了jar-version1.0;
    那么等到第一个webapp要用WebAppClassLoader想加载jar-version2.0时, 直接去CommonClassLoader中去取了, 直接得到了jar-version1.0, 而jar-version2.0就不会再被加载。所以要打破双亲委派,让其不向上抛掷

什么时候需要打破双亲委派机制?

即面临上述的缺点的时候:

  1. 父类加载器想要使用子类加载器加载原本父类够不到的class文件时
  2. 想要同时加载同一个class的不同版本时

如何打破双亲委派机制?

  1. 使用当前线程上下文加载器(当前线程上下文加载器默认是应用类加载器) (JDBC用的是这种)
  2. 使用自定义类加载器, 重写loadclass方法, 不去父类中检查 (Tomcat用的是这种)

自定义类加载器也可以不打破双亲委派,主要是看你是否重写loadclass方法搞事情

什么时候需要自定义类加载器?

  1. 想加载不在classpath路径下的class文件
  2. 想要加载一个相同全类名但不同版本的class文件, 使之共存
  3. 热部署时, 只需要GC回收掉之前的老的类, 然后重新创建自定义类加载器实例进行加载这个新的class文件

Tomcat自定义类加载的好处:

  1. 实现了父类Common加载器加载的class, 子类加载器就不用重复加载了
  2. 可以同时使用不同版本的jar(重写loadclass打破双亲委派机制)
  3. JSP类加载器对JSP的热部署

怎么自定义类加载器?

class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String classFilePath) throws ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream( );
        try {
            Files.copy(Paths.get(classFilePath), baos);
            byte[] bytes = baos.toByteArray( );
            String className = classFilePath.split("/")[2].replace(".class", "");
            return defineClass(className, bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace( );
            throw new ClassNotFoundException("class文件找不到");
        }
    }
}

// 使用
MyClassLoader myClassLoader1 = new MyClassLoader( );
MyClassLoader myClassLoader2 = new MyClassLoader( );

Class<?> userClzVersion1 = myClassLoader1.loadClass("C:/version1/com.hellosrc.User.class");
Class<?> userClzVersion2 = myClassLoader2.loadClass("C:/version1/com.hellosrc.User.class");

// 不打破双亲委派机制
// 虽然打印结果为false, 但实际上User类只被加载了一次, 
// 因为myClassLoader1和myClassLoader2 本质都是用共同的父类加载器(即系统类加载器)去加载
System.out.println(userClzVersion1 == userClzVersion2);

// 触发静态代码块, 发现只被执行了一次, 再次印证了不打破双亲委派机制时, 不能把一个全类名相同的类重复加载
Object userVersion2 = userClzVersion1.newInstance( );
Object userVersion1 = userClzVersion1.newInstance( );

HelloWorld.class可以被JVM加载多次吗?

可以,就像Tomcat那样打破双亲委派机制后,用不同的类加载器去加载HelloWorld.class,而且还可以加载不同版本

SPI服务发现机制

如在Dubbo或JDBC或Spring中, 在类路径下放的META-INF/services/文件的内容会被程序读取, 这文件里写的就是要加载的注册的服务名;

JDBC因此可以不用显示写出Class.forName(“驱动名”), 就可以直接注册驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值