首页 > 其他分享 >JVM类加载器与双亲委派机制

JVM类加载器与双亲委派机制

时间:2024-06-21 23:31:43浏览次数:12  
标签:委派 Java 自定义 ClassLoader 双亲 JVM 加载

通过上一篇Java的类加载机制相信大家已经搞明白了整个类加载从触发时机,接着我们就来看下类加载器,因为类加载机制是有加载器实现的。 

类加载器的分类

启动类加载器

Bootstrap ClassLoader 是 Java 虚拟机(JVM)的一部分,它负责加载 Java 核心库,也就是 Java Runtime Environment (JRE) 中的类。这些类通常位于 JRE 的 lib 目录下的 rt.jar 文件中,以及可能的其他 JAR 文件,比如 jsse.jar(Java Secure Socket Extension)等。

Bootstrap ClassLoader 是一个虚拟的类加载器,它不继承自 java.lang.ClassLoader,也不可以被直接引用或实例化。它是 JVM 的一部分,并且是所有类加载器的父加载器。当 JVM 启动时,Bootstrap ClassLoader 首先加载 rt.jar 中的类,然后这些类可以被其他类加载器使用。

Bootstrap ClassLoader 的主要作用包括:

  1. 加载 Java 核心库,如 java.lang 包中的类。
  2. 作为类加载器层次结构的根,为其他类加载器提供基础。

扩展类加载器

Extension ClassLoader 是 Java 虚拟机(JVM)中的一个系统类加载器,它负责加载 Java 扩展目录中的类库。以下是关于 Extension ClassLoader 的几个关键点:

  1. 加载职责:Extension ClassLoader 专门用来加载 lib/ext 目录或者由系统属性 java.ext.dirs 指定的其他目录中的类库。这些类库通常包括 Java 平台的标准扩展,例如一些常用的第三方库。

  2. 类加载顺序:在 JVM 的类加载体系中,Extension ClassLoader 位于 Bootstrap ClassLoader 之后。当一个类请求被提交到 JVM 时,Bootstrap ClassLoader 首先尝试加载,如果找不到相应的类,请求会传递给 Extension ClassLoader。

  3. 双亲委派模型:Extension ClassLoader 遵循 Java 的双亲委派模型,这意味着在尝试自己加载类之前,它会先委托给父类加载器(在这个情况下是 Bootstrap ClassLoader)进行加载。

  4. 配置灵活性:开发者可以通过设置 java.ext.dirs 系统属性来指定多个扩展目录,使得 JVM 可以在启动时从这些目录中加载类库。

应用类加载器

Application ClassLoader 是 JVM 中的一个系统类加载器,它的作用是加载应用程序类路径(ClassPath)上指定的类库。以下是 Application ClassLoader 的几个关键点:

1. 加载职责:Application ClassLoader 主要负责加载用户编写的 Java 应用程序代码。这些代码通常位于项目的 `bin` 或 `classes` 目录下,或者是通过 JAR 文件提供的。

2. 类加载顺序:在 JVM 的类加载器层级中,Application ClassLoader 位于 Extension ClassLoader 之后。这意味着,如果一个类同时在扩展目录和应用程序类路径中存在,Extension ClassLoader 会优先加载它。

3. 双亲委派模型:和 Extension ClassLoader 一样,Application ClassLoader 也遵循双亲委派模型。当它需要加载一个类时,会先委托给父类加载器(Extension ClassLoader)尝试加载,如果父类加载器无法加载,Application ClassLoader 才会尝试从应用程序类路径加载。

4. 灵活性:开发者可以通过设置系统属性 `java.class.path` 或使用 `-cp` 或 `-classpath` 命令行选项来指定应用程序类路径,从而控制 Application ClassLoader 加载的类库。

简而言之,Application ClassLoader 是我们程序员在 Java 应用程序开发过程中接触最多的类加载器,它负责将编写的 Java 类加载到 JVM 中,是 Java 应用程序运行的基础。其实你大致就理解为去加载你写好的Java代码,这个类加载器就负责加载你写好的那些类到内存里

自定义类加载器

自定义类加载器是 Java 动态加载类的一种强大机制,它允许开发者根据特定的需求来加载类。这种机制特别有用在需要动态加载或更新类定义的场景中,例如在热部署、模块化应用、或者需要从非标准源加载类文件等情况下。以下是 自定义类加载器的几个关键点:

  1. 继承性:自定义类加载器需要继承 java.lang.ClassLoader 类,并重写 findClass 方法来实现自定义的类查找逻辑。

  2. 加载逻辑:开发者可以在 findClass 方法中实现自己的类加载逻辑,例如从数据库、网络、文件系统等非标准位置加载类文件。

  3. 双亲委派模型:自定义类加载器同样遵循 Java 的双亲委派模型。在尝试加载类之前,它会委托给父类加载器,如果父类加载器无法加载,自定义类加载器才会介入。

  4. 隔离性:自定义类加载器可以创建与系统类加载器和应用程序类加载器隔离的类,这有助于实现模块化和防止类冲突。

  5. 安全性:自定义类加载器可以提供额外的安全检查,例如验证类文件的来源或内容,确保加载的类是安全的。

  6. 灵活性:通过自定义类加载器,开发者可以控制类的加载时机、来源和方式,为 Java 应用程序提供更高的灵活性和可扩展性。

  7. 使用场景:自定义类加载器适用于需要动态加载类、实现类版本控制、或者需要加载特定格式类文件的应用程序。

        通过自定义类加载器,Java 应用程序可以突破传统的类加载限制,实现更加灵活和动态的类加载策略。这对于需要高度定制化和模块化的应用程序尤其重要。下面提供简单的代码实例:

import java.io.*;
import java.nio.file.*;
import java.util.logging.*;

public class MyClassLoader extends ClassLoader {

    private static final Logger LOGGER = Logger.getLogger(MyClassLoader.class.getName());

    private final String classPath;
    private final boolean verify;

    public MyClassLoader(String classPath) {
        this(classPath, true);
    }

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = name.replace('.', '/') + ".class";
        byte[] classData = loadClassData(fileName);
        if (classData == null) {
            throw new ClassNotFoundException("Could not find " + fileName + " in " + classPath);
        }
        if (verify) {
            verifyClassData(classData);
        }
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String fileName) throws IOException {
        Path path = Paths.get(classPath, fileName);
        try (InputStream in = Files.newInputStream(path)) {
            return in.readAllBytes();
        }
    }

    private void verifyClassData(byte[] classData) {
        // 这里可以添加对类数据的验证逻辑
        LOGGER.info("Class data verification is not implemented yet.");
    }

    public static void main(String[] args) {
        try {
            String classPath = "path/to/your/classes"; // 替换为实际的类文件路径
            MyClassLoader myClassLoader = new MyClassLoader(classPath);

            Class<?> myClass = myClassLoader.loadClass("com.example.MyClass");
            Object instance = myClass.getDeclaredConstructor().newInstance();

            // 假设 MyClass 有一个 sayHello 方法
            java.lang.reflect.Method sayHelloMethod = myClass.getMethod("sayHello");
            sayHelloMethod.invoke(instance);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Error loading class", e);
        }
    }
}

双亲委派机制

双亲委派机制层级结构

JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器,虽然说是最后一层,但是因为我们可以无限自定义,所以其实就是树的结构,深度可以不断累加。如下图:

为什么需要双亲委派机制?

双亲委派机制是 Java 类加载机制的核心原则之一,其设计具有以下几个重要目的:

  1. 安全性:双亲委派机制确保了 Java 核心库的类不能被随意替换或篡改。因为只有引导类加载器(Bootstrap ClassLoader)才能加载 rt.jar 中的 Java 核心类库,而它是不可扩展的。这防止了恶意代码对 Java 核心库的破坏。

  2. 避免类的重复加载:通过委托给父类加载器,JVM 确保了一个类在 JVM 内只被加载一次。这避免了类的重复加载,节省了内存资源,并且保证了类的唯一性。

  3. 保证加载顺序:双亲委派模型保证了类按照既定的顺序加载,例如,Java 核心库总是首先加载,然后是扩展库,最后是应用程序类。这有助于维护程序的稳定性和可预测性。

  4. 封装性和层次性:双亲委派模型允许 Java 应用程序分层,每一层都有自己的类加载器。这有助于实现模块化,使得不同层次的代码可以独立地更新和替换,而不会影响到其他层次。

  5. 类空间隔离:在复杂的应用程序中,如 Web 容器或 OSGi 框架,双亲委派模型通过使用不同的类加载器来实现类空间的隔离。这使得不同的模块或应用可以加载相同类的不同版本,而不会相互冲突。

  6. 易于实现和维护:双亲委派模型的实现相对简单,逻辑清晰,易于理解和维护。开发者可以专注于实现自己的 findClass 方法,而不必担心类加载的委托逻辑。

  7. 灵活性:虽然双亲委派模型是 Java 类加载的默认策略,但它并不是强制性的。开发者可以通过自定义类加载器来实现特定的类加载逻辑,例如,从数据库或网络加载类。

  8. 优化性能:通过缓存已加载的类(在 ClassLoader 中的 classes 集合中),JVM 可以快速检查类是否已经被加载,从而避免不必要的加载过程,提高性能。

简而言之,双亲委派机制为 Java 类加载提供了一种安全、有效、可预测的策略,有助于维护 Java 应用程序的稳定性和性能。

Tomcat真的破双亲委派机制了嘛?

Tomcat 并没有打破 Java 的双亲委派模型,而是在双亲委派模型的基础上进行了适应 Web 容器需求的扩展。下面是几个关键点来说明这一点:

  1. 双亲委派模型的核心:双亲委派模型的核心是,当一个类加载器收到类加载请求时,它会先委托给父类加载器去尝试加载这个类,直到达到启动类加载器。如果父类加载器无法完成加载任务,才会由子类加载器尝试加载。

  2. Tomcat 类加载器的实现:Tomcat 实现了多个层次的类加载器,包括 Common ClassLoader、Server ClassLoader、Shared ClassLoader 和 Webapp ClassLoader。这些类加载器都遵循双亲委派模型的委派逻辑。

  3. Webapp ClassLoader 的特殊性:虽然 Tomcat 的 Webapp ClassLoader 允许 Web 应用加载自己版本的类,但这是通过实现自己的 findClass 方法来实现的,而不是通过重写 loadClass 方法来绕过双亲委派模型。Webapp ClassLoader 仍然会首先委托父类加载器尝试加载类。

  4. 线程上下文类加载器(TCCL):Tomcat 在执行请求时,会将当前 Web 应用的类加载器设置为 TCCL。这确保了请求处理过程中加载的类首先会使用 Webapp ClassLoader,但这个过程仍然是在双亲委派模型的框架内进行的。

  5. 隔离性和灵活性:Tomcat 的类加载器设计提供了类隔离和版本控制的灵活性,这并不违反双亲委派模型,而是扩展了类加载器的使用场景。

类加载器实际的关系

        双亲委派机制,最早我一直以为是子类继承父类,其实看到源码以后发现实际上并没有继承关系,而是有成员变量取名叫parent,所以‘亲’由此而来;他们之间是组合关系并非是继承嗷!

        说到这里,本来该结束了,突然想到Effective Java中有一条原则是:组合优于继承?有小伙伴知道吗?哈哈这里就不展开了,后续会在设计模式模块好好唠唠。

结合上述类加载器,回头看看类加载机制忘了的小伙伴可以看上篇文章Java的类加载机制,整体流程如下图:

标签:委派,Java,自定义,ClassLoader,双亲,JVM,加载
From: https://blog.csdn.net/weixin_40735063/article/details/139870315

相关文章

  • JVM常见问题
    文章目录1JVM组成1.1JVM由那些部分组成,运行流程是什么?1.2什么是程序计数器?1.3你能给我详细的介绍Java堆吗?元空间(MetaSpace)介绍1.4什么是虚拟机栈1.5堆和栈的区别1.6能不能解释一下方法区?1.5.1概述1.5.2常量池1.5.3运行时常量池1.7你听过直接内存吗?1.8......
  • 【MindSpore学习打卡】初学教程-04数据集 Dataset-使用MindSpore实现高效数据加载与预
    在深度学习的世界里,数据是模型训练的根基。高质量的数据输入不仅能提升模型的性能,还能加速训练过程。MindSpore提供了一个强大的数据引擎,通过数据集(Dataset)和数据变换(Transforms)实现高效的数据预处理。本文将详细介绍如何使用MindSpore加载和处理数据集,并通过具体的示例......
  • JVM内存区域
    目录一、程序计数器(线程私有)二、虚拟机栈(线程私有)三、堆(Heap-线程共享)-运行时数据区四、方法区/永久代(线程共享)五、本地方法区(线程私有)JVM内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA堆、方法区】、直接内存。......
  • CSharp: 未加载文件或程序集
    发布到IIS服务器提示错误:不能多个版本的同一DLL,虽是文件名不同,也是这个报错    未能加载文件或程序集“System.Runtime.CompilerServices.Unsafe,Version=4.0.4.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a”或它的某一个依赖项。找到的程序集清单定义与程......
  • 记录--单页面首屏优化,打包后大小减少64M,加载速度快了13.6秒
     ......
  • JVM:不同垃圾收集算法和垃圾收集器 优缺点总结
     如何判断对象是否为垃圾:引用计数法:对象是否被引用,无法解决对象循环引用导致的问题可达性分析:也叫根搜索法 垃圾收集算法1、标记-清除(Mark-Sweep)示意图深蓝色:有引用深灰色:没引用白色:待分配回收前:对整个引用空间不连续的堆对象遍历回收后:引用的对象空间不变,清除:对全......
  • 镭速传输界面优化之静态文件加载
    镭速一直是众多企业传输大文件和大数据的优选对象,速度快、稳定且安全是市场上传输软件脱颖而出的立杆标签,那么同样在界面优化和体验的强大也能够给企业用户带来许多直观的感受,那么今天我们就来谈谈镭速是如何做到这些的,在界面方面通过静态文件加载能带来怎么样的舒适效果的。......
  • H5移动端加载预览pdf文件——demo
    前言:正常情况下需要在HTML中嵌入本地docx或pdf文件时,我们会有以下解决办法:1.使用<iframe>标签2.使用<embed>标签3.使用<object>标签4.使用第三方库(如PDF.js)当实际操作时,会发现前三种方式在pc端支持,但在移动端不支持,因为这些标签在移动端浏览器中的支持并不统一。为了兼容移......
  • WinDbg: SOSEX 下载,加载和使用帮助
    SOSex是SOS的扩展,由SteveJohnson开发,他是微软的一个员工,开发并免费提供了SOSexfordownload的下载,但该软件并不开源。通常,该扩展不能与其他DLL并行使用,一般使用.loadx:\full\path\tososex.dll来加载。除了简化.NET的调试外,使用命令!dlk还可以在本机环境中用于......
  • Jquery ajax加载等待执行结束再继续执行下面代码操作
    Jquery等待ajax执行完毕再继续执行下面代码的效果,具体代码如下,其实就是将jqueryajax函数的async参数设置为false即可,该参数默认为true:$(document).ready(function(){loadphpernote();window.open('http://www.phpernote.com');});functionloadphpernote(){......