自学内容网 自学内容网

JVM双亲委派机制

Java 虚拟机(JVM)的双亲委派机制是类加载的一种设计模式,用于确保 Java 应用程序中的类加载过程按照一定的顺序进行,以避免类的重复加载和潜在的类冲突。它的核心思想是,当一个类加载器需要加载某个类时,它首先会将该请求委派给父加载器,而不是自己直接去加载类。具体来说,JVM 的类加载器层次结构是以父子关系组织的,顶层是根类加载器(Bootstrap ClassLoader),然后是扩展类加载器(Extension ClassLoader)和应用类加载器(AppClassLoader)。


双亲委派机制的工作原理

  1. 请求处理顺序:每个类加载器都会先将类加载请求传递给父加载器(即父类加载器),如果父类加载器无法加载该类,才会由当前加载器自己来加载。这个顺序保证了父加载器总是优先于子加载器加载类。

  2. 防止类的重复加载:因为父加载器优先加载类,子加载器如果发现父加载器已经加载过该类,就不再重复加载。这样可以避免类的冲突或重复定义,确保一个类在 JVM 中只有唯一的实例。

  3. 加载过程

    • 根类加载器(Bootstrap ClassLoader):加载 JDK 核心类库(例如 java.lang.* 等)。
    • 扩展类加载器(Extension ClassLoader):加载 JDK 的扩展类库,通常位于 lib/ext 目录下。
    • 应用类加载器(AppClassLoader):加载应用程序的类路径下的类,包括 classpath 中的类。


双亲委派机制的优势

  1. 类冲突的避免:通过先委派给父类加载器,确保了核心类库不会被用户自定义类覆盖。例如,java.lang.String 类的加载请求会被委派给根类加载器,避免了应用类加载器加载自己定义的 String 类,防止了冲突。

  2. 系统的稳定性:通过优先加载核心和标准库类,确保了 JDK 提供的类库始终是最新和最可靠的,而用户自己的类则在此基础上加载,保持了系统的稳定性和安全性。

  3. 类加载的高效性:因为父类加载器先加载,如果已经加载过某个类,则子加载器无需再加载,提高了加载效率。


双亲委派机制的实现

JVM 中的类加载器实现了 ClassLoader 类,ClassLoader 类有一个重要的方法 loadClass(String name),它是类加载的核心方法。默认情况下,loadClass 会先尝试委派给父类加载器加载类:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    // 委派给父类加载器加载
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        if (parent != null) {
            c = parent.loadClass(name);
        }
    }
    if (c == null) {
        // 如果父加载器不能加载,再由当前加载器来加载
        c = findClass(name);
    }
    return c;
}

示例:

假设有一个类 MyClass,加载过程如下:

  1. 请求加载 MyClass:应用类加载器(AppClassLoader)收到加载请求。
  2. 父加载器的委派:应用类加载器会首先将请求委派给扩展类加载器(ExtClassLoader),再由扩展类加载器委派给根类加载器(Bootstrap ClassLoader)。
  3. 根类加载器检查:根类加载器负责加载核心类库,如 java.lang.*。如果 MyClass 不属于核心类库,它会返回 null,告知扩展类加载器无法加载。
  4. 扩展类加载器检查:扩展类加载器检查其路径(如 lib/ext),如果仍然找不到类 MyClass,则返回 null
  5. 应用类加载器加载:如果以上步骤都不能加载 MyClass,应用类加载器会自己去类路径中查找并加载该类。

它确保了类加载的顺序性、避免了类冲突、提高了加载效率。通过将加载任务委派给父加载器,Java 保证了核心类库始终优先于应用类加载,从而保持了系统的稳定性和一致性。


双亲委派机制的异常处理

双亲委派机制并不是万能的。在某些情况下,我们需要修改或自定义类加载器的行为。例如,若我们希望自定义某些类的加载顺序或加载特定目录下的类,就需要通过继承 ClassLoader 类来实现。

自定义类加载器

假设我们要创建一个新的类加载器,专门加载某个特定路径下的类,并且不希望将加载请求委派给父加载器。我们可以通过重写 findClass 方法来控制加载过程,但仍然可以保留双亲委派机制的优势。自定义类加载器的典型示例如下:

public class MyClassLoader extends ClassLoader {
    private String classPath;

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

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 查找指定路径下的类文件并加载
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException(name);
        }
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String className) {
        // 从指定路径加载类的字节码
        // 这个方法会从 classPath 路径加载字节码文件
        String path = classPath + "/" + className.replace('.', '/') + ".class";
        // 读取文件内容并返回字节数组
        // 示例代码省略实际的文件读取
        return null;
    }
}
调整双亲委派机制

在自定义类加载器时,可能会有需求希望绕过父加载器。比如,当我们有多个不同版本的第三方库时,可能需要先加载本地版本的类,而不委派给父加载器。为此,我们可以在 loadClass 方法中修改委派顺序。

 

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    // 不委派给父加载器,直接由当前加载器处理
    if (name.startsWith("com.mycompany")) {
        return findClass(name);
    }
    // 否则,委派给父加载器
    return super.loadClass(name);
}

 

这种调整通常用于避免加载错误的类,或者处理特殊版本的类冲突。

双亲委派机制的优缺点

优点
  1. 稳定性:通过委派给父加载器,保证了核心类(如 java.lang 类)始终是由系统类加载器加载的,从而避免了核心类被应用程序中的类覆盖的情况。
  2. 减少重复加载:父类加载器已经加载过某个类,子类加载器就不会再进行加载,这样可以提高效率并避免类加载冲突。
  3. 避免类的篡改:由于父类加载器会优先加载类,应用类加载器无法随意修改系统类库的实现,有助于防止不安全的代码或漏洞。
缺点
  1. 灵活性差:双亲委派机制的设计虽然能确保稳定性,但它会限制类加载的灵活性。如果有需要动态加载不同版本的同一类的场景,双亲委派机制可能就不适用了。
  2. 性能开销:每次加载类时都要经过父类加载器的层层委派,可能会产生一些性能开销,尤其是在层级比较深的类加载器结构中,可能会影响类加载速度。
  3. 特定需求处理复杂:当我们需要动态加载不同来源的类时(比如插件框架),可能需要自定义加载器并打破双亲委派机制,这样需要更多的控制逻辑,增加了代码复杂度。

双亲委派机制与 ClassLoader 的多态性

Java 的类加载器机制是多态的,它允许通过自定义加载器实现灵活的加载策略。ClassLoader 是一个抽象类,我们可以根据实际需求选择不同的加载器,甚至组合多个加载器。ClassLoader 的多态性使得我们可以为不同的类路径配置不同的加载器。

例如,如果我们有两个插件系统,一个是内部开发的插件,一个是第三方的插件,我们可以为每个插件系统创建不同的类加载器。这样即使有相同的类名,两个系统也不会互相冲突,确保了插件的独立性。

public class PluginClassLoader extends ClassLoader {
    // 插件加载逻辑
}

public class InternalPluginClassLoader extends ClassLoader {
    // 内部插件加载逻辑
}

现实中的应用

  1. Web 应用服务器:比如 Tomcat、Jetty 等,通常会使用双亲委派机制来加载应用程序的类库。Tomcat 会为每个 Web 应用创建独立的类加载器,同时采用双亲委派机制来加载系统类库和核心类。
  2. OSGi 框架:OSGi 是一个模块化框架,它通过自定义类加载器来实现动态模块加载,同时有时也需要修改双亲委派机制以适应动态模块的加载需求。
  3. 插件架构:在一些大型的应用程序中(如 IDE、游戏引擎等),插件系统的设计通常会依赖于类加载器来加载插件。为避免插件和主程序的类库冲突,插件类加载器需要打破父类加载器的限制,实现独立加载。

 


原文地址:https://blog.csdn.net/weixin_45710998/article/details/144103597

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!