深入理解 JVM 类加载器的双亲委派机制

目录

一、引言

二、JVM 类加载器概述

(一)类加载器的作用

(二)常见的类加载器

三、双亲委派机制的原理

(一)基本概念

(二)工作流程

(三)代码示例

四、双亲委派机制的优点

(一)避免类的重复加载

(二)保证 Java 核心类库的安全性

(三)保证类的唯一性

五、双亲委派机制的破坏

(一)自定义类加载器破坏双亲委派机制

六、结论


一、引言

在 Java 编程中,JVM(Java 虚拟机)的类加载器是一个至关重要的组件,它负责将类的字节码文件加载到 JVM 中。而双亲委派机制是类加载器的核心工作模式,理解这一机制对于掌握 Java 类加载的原理和解决类加载相关的问题具有重要意义。

二、JVM 类加载器概述

(一)类加载器的作用

类加载器的主要作用是将类的字节码文件(.class 文件)加载到 JVM 的内存中,并将其转化为 java.lang.Class 对象,以便后续创建该类的实例。

(二)常见的类加载器

JVM 中存在多种类加载器,常见的有以下几种:

  1. 启动类加载器(Bootstrap ClassLoader):它是最顶层的类加载器,由 C++ 实现,负责加载 Java 的核心类库,如 java.lang 包下的类。它加载的路径通常是 JDK 的 lib 目录下的核心类库。
  2. 扩展类加载器(Extension ClassLoader):由 Java 实现,负责加载 JDK 的 lib/ext 目录下的类库,这些类库是 Java 核心类库的扩展。
  3. 应用程序类加载器(Application ClassLoader):也称为系统类加载器,同样由 Java 实现,负责加载用户类路径(classpath)下的类库,一般是我们自己编写的 Java 类。
  4. 自定义类加载器(Custom Class Loader):开发者可以根据自己的需求自定义类加载器,用于加载特定路径或格式的类文件。

三、双亲委派机制的原理

(一)基本概念

双亲委派机制是指当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,因此所有的类加载请求最终都会传送到顶层的启动类加载器。只有当父类加载器无法完成加载请求时,子加载器才会尝试自己去加载。

(二)工作流程

以下是双亲委派机制的具体工作流程:

  1. 当一个类加载器收到类加载请求时,它会先检查该类是否已经被加载过。如果已经加载过,则直接返回该类的 Class 对象。
  2. 如果该类没有被加载过,类加载器会将加载请求委派给它的父类加载器。
  3. 父类加载器会重复步骤 1 和 2,直到请求到达启动类加载器。
  4. 启动类加载器会尝试加载该类,如果能加载成功,则返回该类的 Class 对象;如果无法加载,则将请求返回给子类加载器。
  5. 子类加载器收到父类加载器无法加载的反馈后,会尝试自己加载该类。如果能加载成功,则返回该类的 Class 对象;如果仍然无法加载,则抛出 ClassNotFoundException 异常。

(三)代码示例

以下是一个简单的示例,展示了类加载器的双亲委派机制:

public class ClassLoaderExample {
    public static void main(String[] args) {
        // 获取应用程序类加载器
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("应用程序类加载器: " + appClassLoader);

        // 获取应用程序类加载器的父类加载器,即扩展类加载器
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("扩展类加载器: " + extClassLoader);

        // 获取扩展类加载器的父类加载器,即启动类加载器(在 Java 中无法直接获取,返回 null)
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("启动类加载器: " + bootstrapClassLoader);

        try {
            // 尝试加载一个类
            Class<?> clazz = appClassLoader.loadClass("java.lang.String");
            System.out.println("类加载器: " + clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们首先获取了应用程序类加载器,然后通过 getParent() 方法获取了它的父类加载器(扩展类加载器)和扩展类加载器的父类加载器(启动类加载器)。最后,我们尝试加载 java.lang.String 类,由于该类属于 Java 核心类库,会由启动类加载器加载,因此输出的类加载器为 null

四、双亲委派机制的优点

(一)避免类的重复加载

由于类加载请求会先委派给父类加载器,当父类加载器已经加载了某个类时,子类加载器就不会再重复加载该类,从而避免了类的重复加载,提高了内存的使用效率。

(二)保证 Java 核心类库的安全性

Java 核心类库由启动类加载器加载,无论哪个类加载器请求加载这些核心类,最终都会由启动类加载器完成加载。这样可以确保 Java 核心类库的安全性,防止恶意代码通过自定义类加载器替换 Java 核心类。

(三)保证类的唯一性

由于双亲委派机制的存在,同一个类只会被一个类加载器加载一次,从而保证了类的唯一性。这对于 Java 程序的正常运行至关重要,因为如果同一个类被不同的类加载器加载多次,可能会导致类的比较和类型转换等操作出现问题。

五、双亲委派机制的破坏

虽然双亲委派机制有很多优点,但在某些特殊情况下,我们可能需要破坏这种机制。例如,在一些 Java Web 应用服务器中,为了实现类的隔离和热部署,会采用自定义的类加载器并破坏双亲委派机制。

(一)自定义类加载器破坏双亲委派机制

以下是一个简单的自定义类加载器示例,它破坏了双亲委派机制:

import java.io.*;

public class CustomClassLoader extends ClassLoader {
    private String classPath;

    public CustomClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                return defineClass(name, classData, 0, classData.length);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    private byte[] getClassData(String className) throws IOException {
        String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        try (InputStream is = new FileInputStream(path);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            return bos.toByteArray();
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        CustomClassLoader customClassLoader = new CustomClassLoader("path/to/your/classes");
        Class<?> clazz = customClassLoader.loadClass("com.example.MyClass");
        System.out.println("类加载器: " + clazz.getClassLoader());
    }
}

在上述代码中,我们自定义了一个类加载器 CustomClassLoader,并重写了 findClass 方法,直接从指定路径加载类文件,而不遵循双亲委派机制。

六、结论

双亲委派机制是 JVM 类加载器的核心工作模式,它具有避免类的重复加载、保证 Java 核心类库的安全性和类的唯一性等优点。但在某些特殊情况下,我们可能需要破坏这种机制以满足特定的需求。深入理解双亲委派机制对于掌握 Java 类加载的原理和解决类加载相关的问题具有重要意义。

猜你喜欢

转载自blog.csdn.net/weixin_73355603/article/details/147114645