JVM-类加载器和双亲委派机制

什么是类加载器?

类加载器是Jvm的重要组成之一(类加载器、运行时数据区、执行引擎、本地库接口、本地方法库),负责读取java字节码并将其加载到Jvm中的组件

类加载器的分类

Java中的类加载器可以分为以下几种:

1. 启动类加载器(Bootstrap ClassLoader)
  • 定义:这是最顶层的类加载器,用于加载Java核心类库(如java.lang.*java.util.*等)。
  • 实现:启动类加载器是由本地代码(通常是C/C++)实现的,并不是一个Java类。
  • 加载路径:它加载位于<JAVA_HOME>/lib目录下的类文件。
2. 扩展类加载器(Extension ClassLoader)
  • 定义:用于加载扩展类库(通常是标准类库的扩展)。
  • 实现:由sun.misc.Launcher$ExtClassLoader类实现。
  • 加载路径:它加载位于<JAVA_HOME>/lib/ext目录下的类文件,或者由java.ext.dirs系统属性指定的路径。
3. 应用程序类加载器(Application ClassLoader)
  • 定义:又称为系统类加载器(System ClassLoader),用于加载应用程序的类文件。
  • 实现:由sun.misc.Launcher$AppClassLoader类实现。
  • 加载路径:它加载由CLASSPATH环境变量或java.class.path系统属性指定的路径中的类文件。
4. 自定义类加载器(Custom ClassLoader)
  • 定义:自定义类加载器是由开发者定义的类加载器,用于从特定来源加载类文件。通过自定义类加载器,可以实现从文件系统、网络、数据库等非标准路径加载类文件。
  • 实现:自定义类加载器通常通过继承java.lang.ClassLoader类并重写其方法来实现。例如,重写findClass方法来定义类的加载逻辑。
  • 加载路径:自定义类加载器可以从任何指定的路径加载类文件,这取决于自定义的实现逻辑。

类加载器的双亲委派模型

Java采用了双亲委派模型(Parent Delegation Model)来管理类加载器的层次结构。在这种模型中,每个类加载器在尝试加载类之前,会先委派给它的父类加载器加载。只有当父类加载器无法找到该类时,子加载器才会尝试加载。

例如当一个自定义类加载器试图加载一个类,它会先委派应用程序类加载器,如何应用程序类加载器会委派扩展类加载器,扩展类加载器会委派启动类加载器,启动类加载器先看一下自己有没有这个类,没有就让扩展类去加载,有的话直接自己加载,同理,扩展类也会先看有没有,没有的话让应用程序类加载,应用程序类也是同理。假如所有类加载器都没有该类,则会抛出ClassNotFoundException异常。

如图,自定义类加载器的父类加载器通常是应用程序类加载器(可改),应用程序的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。值得一提的是,父类加载器是用类种的一个成员变量(parent)存储的,而因为启动类加载器特俗的地位和职责,无法在java中获取到,所以实际上扩展类加载器的parent存放的是一个null,实际需要调用启动类加载器时会通过特殊的方法实现。通过getClassLoader()方法去获得启动类加载器返回的也是null。

双亲委派模型的好处
  1. 安全性:防止核心类库被自定义类覆盖,提高系统的安全性。
  2. 避免重复加载:确保类只被加载一次,提高性能和内存利用率。

打破双亲委派机制的三种方法

一、自定义类加载器

自定义一个类加载,重写loadClass()方法,清除双亲委派机制的代码,实例:

@Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 检查类是否已经加载
        Class<?> c = findLoadedClass(name);

        // 如果类未被加载,尝试加载类
        if (c == null) {
            try {
                // 自定义类加载逻辑
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                // 如果找不到类,再尝试使用父类加载器加载
                if (getParent() != null) {
                    c = getParent().loadClass(name);
                } else {
                    throw new ClassNotFoundException(name);
                }
            }
        }

        // 如果需要链接类
        if (resolve) {
            resolveClass(c);
        }

        return c;
    }
二、利用线程上下文类加载器

调用线程上下文类加载器(通常是应用程序类加载器)来打破双亲委派机制,思路是当一个类的类加载器是启动类或扩展类加载器时,是无法通过双亲委派机制去委派到应用程序类加载器的,假如需要加载的类又只有应用程序类加载器中有,那么此时就可以通过线程上下文类加载器去加载,因为线程上下文类加载器通常保存的是应用程序类加载器。

// 获取当前线程的上下文类加载器
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
利用线程上下文类加载器到底有没有打破双亲委派机制?

在笔者看来是没有直接打破双亲委派机制的,因为线程上下文类加载器依然是按照双亲委派机制去加载类,线程上下文类加载器只是提供了一种更灵活的方式去加载类。

三、OSGI框架的类加载器

OSGI框架自己实现了一套类加载机制打破了双亲委派机制,在OSGI中允许同级类互相委派,并且OSGI还使用类加载器实现了项目的热部署。

总结

类加载器:加载 Java 类字节码到 JVM 的组件,负责类的查找、加载和连接。

类加载器的分类

  1. 启动类加载器(Bootstrap ClassLoader):加载核心 Java 类库。
  2. 扩展类加载器(Extension ClassLoader):加载扩展类库。
  3. 应用类加载器(Application ClassLoader):加载应用程序类。

双亲委派机制:类加载请求先递交给父类加载器处理,避免重复加载和确保核心类安全。

打破双亲委派机制的三种方法

  1. 自定义类加载器:自定义逻辑加载类,绕过父类加载。
  2. 线程上下文类加载器:通过修改线程的上下文类加载器,动态选择类加载器。
  3. 使用 OSGi 框架:OSGi 自带的类加载机制支持模块化和隔离,突破双亲委派。
  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A泽予

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值