JVM 类加载机制及双亲委派模型是什么

发布时间:2021-10-23 16:15:46 作者:柒染
来源:亿速云 阅读:192

JVM 类加载机制及双亲委派模型是什么

1. 引言

Java 虚拟机(JVM)是 Java 程序运行的核心环境,它负责将 Java 字节码转换为机器码并执行。在 JVM 中,类加载机制是一个非常重要的组成部分,它负责将 Java 类加载到内存中,并在运行时动态链接和初始化这些类。类加载机制的核心是双亲委派模型,它确保了类加载的安全性和一致性。

本文将详细介绍 JVM 的类加载机制,并深入探讨双亲委派模型的工作原理及其在 Java 应用中的重要性。

2. JVM 类加载机制概述

2.1 类加载的基本概念

在 Java 中,类加载是指将类的字节码文件(.class 文件)加载到 JVM 的内存中,并在内存中生成对应的 Class 对象的过程。类加载器(ClassLoader)是负责执行这一过程的组件。

类加载的过程通常包括以下几个步骤:

  1. 加载(Loading):查找并加载类的字节码文件。
  2. 验证(Verification):确保加载的字节码文件符合 JVM 规范,防止恶意代码的注入。
  3. 准备(Preparation):为类的静态变量分配内存并设置默认初始值。
  4. 解析(Resolution):将类中的符号引用转换为直接引用。
  5. 初始化(Initialization):执行类的静态初始化代码(如静态代码块和静态变量赋值)。

2.2 类加载器的层次结构

JVM 中的类加载器是按照层次结构组织的,每个类加载器都有一个父类加载器(除了顶层的启动类加载器)。类加载器的层次结构如下:

  1. 启动类加载器(Bootstrap ClassLoader):负责加载 JVM 核心类库(如 java.lang.*),通常由 C++ 实现,是 JVM 的一部分。
  2. 扩展类加载器(Extension ClassLoader):负责加载 Java 扩展库(如 javax.*),位于 jre/lib/ext 目录下。
  3. 应用程序类加载器(Application ClassLoader):负责加载应用程序类路径(Classpath)下的类。
  4. 自定义类加载器(Custom ClassLoader):开发者可以自定义类加载器,用于加载特定位置的类。

2.3 类加载器的双亲委派模型

双亲委派模型是 JVM 类加载机制的核心设计原则。它的基本思想是:当一个类加载器需要加载一个类时,首先会委托其父类加载器去加载,只有在父类加载器无法加载该类时,才会由当前类加载器自行加载。

双亲委派模型的工作流程如下:

  1. 当一个类加载器收到类加载请求时,首先不会自己去加载该类,而是将请求委派给父类加载器。
  2. 父类加载器会重复这一过程,直到请求传递到顶层的启动类加载器。
  3. 如果启动类加载器无法加载该类,则会将请求返回给下一级的扩展类加载器。
  4. 如果扩展类加载器也无法加载该类,则请求会继续向下传递,直到应用程序类加载器。
  5. 如果所有父类加载器都无法加载该类,则当前类加载器会尝试自己加载该类。

双亲委派模型的优点在于:

3. 双亲委派模型的实现细节

3.1 类加载器的 loadClass 方法

在 Java 中,类加载器的 loadClass 方法是实现双亲委派模型的关键。loadClass 方法的默认实现如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 首先检查类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 如果父类加载器不为空,则委派给父类加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 如果父类加载器为空,则委派给启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果父类加载器无法加载该类,则捕获异常并继续
            }

            if (c == null) {
                // 如果父类加载器无法加载该类,则尝试自己加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

从上述代码可以看出,loadClass 方法首先会检查类是否已经被加载,如果没有被加载,则会委派给父类加载器去加载。如果父类加载器无法加载该类,则当前类加载器会尝试自己加载。

3.2 自定义类加载器的实现

在某些情况下,开发者可能需要自定义类加载器,以实现特定的类加载逻辑。自定义类加载器通常需要继承 ClassLoader 类,并重写 findClass 方法。以下是一个简单的自定义类加载器示例:

public class CustomClassLoader extends ClassLoader {
    private String classPath;

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

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

    private byte[] loadClassData(String className) {
        String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        try (InputStream inputStream = new FileInputStream(path);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在这个示例中,CustomClassLoader 类重写了 findClass 方法,从指定的路径加载类的字节码文件,并通过 defineClass 方法将其转换为 Class 对象。

3.3 打破双亲委派模型

在某些特殊情况下,开发者可能需要打破双亲委派模型。例如,某些框架(如 OSGi)需要实现模块化的类加载机制,允许不同模块加载相同类的不同版本。

打破双亲委派模型的方式通常是通过重写 loadClass 方法,直接调用 findClass 方法,而不委派给父类加载器。以下是一个简单的示例:

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 直接调用 findClass 方法,打破双亲委派模型
        Class<?> c = findClass(name);
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 自定义类加载逻辑
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String className) {
        // 加载类的字节码文件
        // ...
    }
}

在这个示例中,CustomClassLoader 类重写了 loadClass 方法,直接调用 findClass 方法,从而打破了双亲委派模型。

4. 双亲委派模型的应用场景

4.1 核心类库的安全性

双亲委派模型确保了核心类库的安全性。由于核心类库由启动类加载器加载,应用程序类加载器无法加载与核心类库同名的类,从而防止了恶意代码替换核心类库中的类。

4.2 类的唯一性

双亲委派模型确保了同一个类在 JVM 中只有一个版本。由于类加载器会首先委派给父类加载器加载类,因此同一个类不会被不同的类加载器重复加载,避免了类的冲突和不一致性。

4.3 模块化类加载

在某些框架(如 OSGi)中,双亲委派模型被打破,以实现模块化的类加载机制。每个模块可以使用自己的类加载器加载类,从而允许不同模块加载相同类的不同版本。

5. 总结

JVM 的类加载机制是 Java 程序运行的基础,而双亲委派模型是类加载机制的核心设计原则。双亲委派模型通过层次化的类加载器结构,确保了类加载的安全性和一致性。尽管在某些特殊情况下需要打破双亲委派模型,但在大多数情况下,双亲委派模型仍然是 Java 类加载的最佳实践。

理解 JVM 的类加载机制和双亲委派模型,对于深入理解 Java 程序的运行机制、排查类加载相关的问题以及实现自定义类加载器都具有重要意义。

推荐阅读:
  1. JVM的类加载机制和应用
  2. 什么是JVM

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jvm

上一篇:如何掌握ThreadLocal的相关知识点

下一篇:java类对象底层是如何创建的

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》