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 自带的类加载机制支持模块化和隔离,突破双亲委派。
相关推荐
yuanbenshidiaos7 小时前
c++---------数据类型
java·jvm·c++
java1234_小锋10 小时前
JVM对象分配内存如何保证线程安全?
jvm
40岁的系统架构师14 小时前
1 JVM JDK JRE之间的区别以及使用字节码的好处
java·jvm·python
寻找沙漠的人14 小时前
理解JVM
java·jvm·java-ee
我叫啥都行14 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
bufanjun00117 小时前
JUC并发工具---ThreadLocal
java·jvm·面试·并发·并发基础
东阳马生架构1 天前
JVM简介—1.Java内存区域
jvm
工程师老罗1 天前
Android笔试面试题AI答之SQLite(2)
android·jvm·sqlite
Qzer_4071 天前
jvm字节码中方法的结构
jvm
奇偶变不变2 天前
RTOS之事件集
java·linux·jvm·单片机·算法