当前位置: 首页 > article >正文

Java 中的引导类加载器(Bootstrap ClassLoader) 详解

1. 引导类加载器的基本概念

        引导类加载器(Bootstrap ClassLoader)是 Java 虚拟机(JVM)中最顶层的类加载器,负责加载 Java 运行时的核心类库,如 java.lang.*java.util.* 等包中的类。它也被称为原始类加载器(Primordial ClassLoader)。与其他类加载器(如扩展类加载器和应用程序类加载器)不同,引导类加载器并不是用 Java 语言实现的,而是由 JVM 的原生代码(通常是 C/C++)实现的,因此在 Java 代码中无法直接访问它的实例。

        引导类加载器的主要职责是加载位于 <JAVA_HOME>/jmods(Java 9 及以上版本)或 <JAVA_HOME>/lib/rt.jar(Java 8 及以下版本)中的核心类文件。这些类是 JVM 运行的基础,例如 ObjectStringClass 等。没有这些类的加载,JVM 无法启动。


2. 类加载器体系结构与引导类加载器的地位

Java 中的类加载器遵循委托模型(Delegation Model),形成一个层次结构:

  • 引导类加载器(Bootstrap ClassLoader):顶层,无父类加载器。
  • 平台类加载器(Platform ClassLoader)(Java 9 之前称为扩展类加载器):负责加载模块化的平台特定类。
  • 应用程序类加载器(Application ClassLoader):加载用户应用程序的类路径(classpath)中的类。
  • 用户自定义类加载器:开发者可以继承 java.lang.ClassLoader 创建自定义加载器。

        引导类加载器是这个层次结构的起点,所有其他类加载器(如 ClassLoader 的实例)最终都依赖于它加载的核心类。它的特殊性在于:

  • 无父类加载器:它是唯一的没有父类的加载器。
  • 原生实现:不是 Java 对象,因此在 Java 中表现为 null
  • 启动 JVM 的第一步:JVM 启动时,引导类加载器由原生代码初始化,加载必要的运行时类。

3. 底层原理:引导类加载器的实现

        引导类加载器不是一个 Java 类,而是 JVM 内部的一部分,通常由 JVM 的实现(如 OpenJDK/HotSpot)用 C/C++ 编写。以下是其底层原理的分析:

3.1 JVM 启动流程中的引导类加载器

        JVM 启动时,会执行一个原生入口函数(例如 HotSpot 中的 main 函数),加载并初始化 JVM。在这个过程中,引导类加载器被创建并用于加载核心类库

以下是简化的启动流程:

  1. 加载 JVM 动态库:操作系统加载 JVM 的动态链接库(如 libjvm.so 或 jvm.dll)。
  2. 初始化 JVM:调用原生函数(如 JNI_CreateJavaVM)创建 JVM 实例。
  3. 创建引导类加载器:JVM 用原生代码实现一个类加载器,负责从 <JAVA_HOME>/lib 或 <JAVA_HOME>/jmods 中读取核心类文件。
  4. 加载核心类:引导类加载器将核心类的字节码加载到 JVM 的内存中(如方法区)。
3.2 原生代码中的实现

        在 OpenJDK 的 HotSpot 实现中,引导类加载器的逻辑主要位于 classLoader.cpp 和 systemDictionary.cpp 等文件中。以下是一个简化的伪代码表示:

// HotSpot 中的引导类加载器逻辑(伪代码)
void bootstrap_class_loader_init() {
    // 设置 bootstrap classpath(例如 <JAVA_HOME>/jmods)
    char* bootstrap_path = get_bootstrap_classpath();
    
    // 加载核心类(如 java.lang.Object)
    load_class_from_path("java.lang.Object", bootstrap_path);
    
    // 初始化其他核心类
    load_class_from_path("java.lang.Class", bootstrap_path);
    load_class_from_path("java.lang.String", bootstrap_path);
}

        这些类加载完成后,JVM 才能创建后续的 Java 类加载器(如 sun.misc.Launcher 中的 AppClassLoader 和 ExtClassLoader)。

3.3 为什么返回 null?

        在 Java 中,调用某个类的 getClassLoader() 方法时,如果该类是由引导类加载器加载的,会返回 null。这是因为引导类加载器不是 Java 对象,无法用 ClassLoader 类型表示。例如:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader()); // 输出 null
    }
}

这是设计上的约定,表示该类由顶层的原生加载器加载。


4. 源代码层面的分析

        虽然引导类加载器本身没有 Java 源代码,但我们可以从 java.lang.ClassLoader 的实现中理解它的作用。ClassLoader 是一个抽象类,所有其他类加载器(如应用程序类加载器)都继承自它。以下是 ClassLoader 中与引导类加载器相关的关键方法:

4.1 loadClass 方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 检查类是否已加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 如果有父类加载器,委托给父类加载器
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                // 如果没有父类加载器,调用原生方法加载(引导类加载器)
                c = findBootstrapClassOrNull(name);
            }
        }
        if (c == null) {
            // 如果父类加载器没找到,当前加载器尝试加载
            c = findClass(name);
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
  • 关键点:当 parent 为 null 时,JVM 调用原生方法 findBootstrapClassOrNull,这是引导类加载器的入口。
  • 底层实现findBootstrapClassOrNull 是通过 JNI(Java Native Interface)调用到 HotSpot 的 C++ 代码,由引导类加载器完成加载。
4.2 HotSpot 中的对应实现

在 HotSpot 的 classLoader.cpp 中,有类似以下逻辑:

static Klass* find_bootstrap_class(JVM* vm, const char* name) {
    // 从 bootstrap classpath 中查找类
    Symbol* class_name = vmSymbols::from_string(name);
    return SystemDictionary::resolve_or_null(class_name, null /* class_loader */, null /* protection_domain */);
}

这个函数从引导类路径中查找并加载类,返回给 Java 层。


5. 工作原理:委托模型与引导类加载器

引导类加载器遵循 Java 类加载的三大原则:

  1. 委托原则(Delegation)
    • 类加载请求首先由子类加载器(如应用程序类加载器)发起,然后逐级委托给父类加载器,直到引导类加载器。
    • 例如,加载 java.lang.String 时,应用程序类加载器委托给平台类加载器,再委托给引导类加载器,最终由引导类加载器完成。
  2. 可见性原则(Visibility)
    • 引导类加载器加载的类对所有子类加载器可见,但子类加载器加载的类对引导类加载器不可见。
    • 例如,String 类对所有加载器可见,但用户定义的类对引导类加载器不可见。
  3. 唯一性原则(Uniqueness)
    • 引导类加载器确保核心类只加载一次,避免重复加载。
示例流程

假设加载一个类 com.example.MyClass

  1. 应用程序类加载器收到请求,检查是否已加载,未找到。
  2. 委托给平台类加载器,未找到。
  3. 委托给引导类加载器,检查 <JAVA_HOME>/jmods,未找到。
  4. 请求返回到平台类加载器,检查模块路径,未找到。
  5. 返回到应用程序类加载器,从 classpath 加载 MyClass

6. Java 9 之后的模块化变化

在 Java 9 中引入了模块系统(Jigsaw 项目),引导类加载器的职责有所调整:

  • Java 8 及之前:加载 rt.jar 和 tools.jar 中的类。
  • Java 9 及之后:加载模块化的 .jmod 文件(如 java.base.jmod),rt.jar 被移除。
  • 平台类加载器:取代了原来的扩展类加载器,负责加载非核心的模块化类。

        引导类加载器仍然负责加载 java.base 模块中的核心类(如 java.lang.Object),但模块系统的引入使得类加载更加灵活和模块化。


7. 总结

引导类加载器是 JVM 的基石,具有以下特点:

  • 原生实现:由 C/C++ 编写,非 Java 类。
  • 核心职责:加载 JVM 运行所需的核心类库。
  • 委托模型:作为类加载器层次结构的顶端,确保类加载的顺序和唯一性。
  • 不可见性:在 Java 中表现为 null,无法直接操作。

        从底层看,它是 JVM 启动的关键组件;从源代码看,它通过 JNI 与 Java 的 ClassLoader 体系协作,共同完成类的动态加载。理解引导类加载器不仅有助于深入掌握 JVM 内部机制,还能更好地调试类加载相关问题(如 ClassNotFoundException)。


        上面的内容不太易于理解,因此我下面用更加通俗易懂的解释,方便读者理解。

1. 什么是引导类加载器?它像个“开机助手”

        想象一下,你打开电脑时,系统需要先加载一些基本程序(比如操作系统的核心部件),才能运行其他软件。在 Java 世界里,引导类加载器就像是 Java 虚拟机(JVM)的“开机助手”。它的任务是帮 JVM 加载最基础、最重要的代码(比如 java.lang.Object 和 java.lang.String),没有这些代码,JVM 就没法工作。

  • 它的地位:它是 JVM 里最顶层的“搬运工”,专门负责搬运核心工具箱(核心类库)。
  • 特别之处:它不是用 Java 写的,而是用更底层的语言(C 或 C++)实现的,所以在 Java 程序里看不到它的“身影”,就像个隐形助手。

2. 它搬运什么?从哪里搬?

        引导类加载器的工作是把 Java 的“核心工具箱”搬到内存里。这个工具箱里装的是 Java 运行必需的基础代码,比如:

  • java.lang.Object:所有 Java 对象的“祖先”。
  • java.lang.String:用来处理文字的工具。
  • java.util.ArrayList:一个可以装很多东西的“动态口袋”。

这些工具箱文件通常存放在你电脑上 Java 安装目录的一个特殊文件夹里:

  • 在老版本 Java(8 及之前),它从 <JAVA_HOME>/lib/rt.jar 文件里搬。
  • 在新版本 Java(9 及之后),它从 <JAVA_HOME>/jmods 文件夹里的模块文件(比如 java.base.jmod)搬。

简单说,它就像个搬运工,专门从“仓库”里把最重要的工具搬出来,让 JVM 可以开始干活。


3. 它是怎么工作的?像个团队协作的“领队”

        在 Java 里,加载代码(类)是个团队合作的过程,叫做“委托模型”。有好几个搬运工(类加载器),每个人负责不同的任务,而引导类加载器是这个团队的“领队”。

工作流程(用生活例子比喻):

假设你要找一本书:

  1. 你先问你旁边的朋友(应用程序类加载器):“你有这本书吗?”
  2. 朋友说:“我没有,我问问我哥(平台类加载器)。”
  3. 哥说:“我也没有,我问问老大(引导类加载器)。”
  4. 老大(引导类加载器)去仓库(核心类库)找,找到了就给你,没找到就说:“我也没有,你们自己想办法吧。”
  5. 如果老大没找到,任务就一层一层传回来,最后你自己去书架(应用程序路径)找。
在 Java 里:
  • 每个搬运工(类加载器)先问自己的“上级”能不能搬这个类。
  • 引导类加载器是最高级,它会先检查核心工具箱(<JAVA_HOME>/jmods),如果有就搬过来。
  • 如果没有,它就告诉下面的搬运工:“我没找到,你们自己去找吧。”

这种“先问上级”的方式保证了重要工具只搬一次,不会重复浪费力气。


4. 底层原理:它是怎么被“唤醒”的?

        引导类加载器不是 Java 程序员写的,而是藏在 JVM 内部的“秘密武器”。它是用 C/C++ 语言写的,跟 JVM 一起“出生”。

开机过程(简单版):
  1. 你运行一个 Java 程序(比如 java HelloWorld)。
  2. 操作系统先找到 JVM 的“核心文件”(比如 libjvm.so),启动它。
  3. JVM 一启动,就用 C/C++ 代码造出引导类加载器。
  4. 引导类加载器马上开始干活,从核心工具箱里搬出 ObjectString 等类,让 JVM 能正常运转。
为什么看不到它?

        在 Java 里,如果你问:“String 是谁搬来的?”(用代码 String.class.getClassLoader()),答案是 null。这是因为引导类加载器不是 Java 对象,它是个“隐形人”,用 C/C++ 写的,所以 Java 里没法直接“摸”到它。


5. 源代码里能看到什么?“间接证据”

        虽然引导类加载器本身没有 Java 代码,但我们可以从 Java 的 ClassLoader 类里找到它的“影子”。ClassLoader 是所有其他搬运工的“模板”,里面有一些线索:

一个关键方法:loadClass
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 先检查这本书(类)是不是已经搬过来了
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        // 如果没有,问上级搬运工
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            // 如果没有上级,我就找引导类加载器
            c = findBootstrapClassOrNull(name);
        }
    }
    if (c == null) {
        // 上级都没找到,我自己搬
        c = findClass(name);
    }
    return c;
}
  • 翻译一下
    • “我要搬个类(比如 java.lang.String)。”
    • “我先问问上级(parent),有没有这个类。”
    • “如果没有上级(parent 是 null),就找引导类加载器帮忙。”
    • “引导类加载器会用它的秘密方法(C/C++ 写的)去搬。”
秘密方法在哪?

        findBootstrapClassOrNull 不是 Java 代码能直接看到的,它通过一个叫 JNI(Java Native Interface)的东西,偷偷跑到 C/C++ 世界,找到引导类加载器的“仓库”,把类搬回来。

C/C++ 里的“仓库管理员”(伪代码):

        在 JVM 的源码(比如 OpenJDK 的 classLoader.cpp)里,引导类加载器大概是这样干活的:

Klass* find_bootstrap_class(const char* name) {
    // 去核心仓库(<JAVA_HOME>/jmods)找类
    if (exists_in_bootstrap_path(name)) {
        return load_class_from_path(name);
    }
    return NULL; // 没找到
}

这个“仓库管理员”检查工具箱,找到类就搬回来,没找到就说“没有”。


6. 为什么它这么重要?

  • 像房子的地基:没有引导类加载器搬来的核心类(比如 Object),JVM 就没法盖房子(运行程序)。
  • 保证秩序:它带头遵守“先问上级”的规则,确保每个类只搬一次,不会乱套。
  • 隐形但强大:虽然看不见它,但它是整个 Java 世界的起点。

7. 新变化:Java 9 之后

        以前(Java 8 及之前),引导类加载器从一个大箱子(rt.jar)里搬东西。现在(Java 9 及之后),Java 把工具箱分成了小份(模块,比如 java.base.jmod),引导类加载器还是负责搬最重要的那份(java.base),但工作方式更灵活了。


8. 总结:用一个比喻

引导类加载器就像一个餐厅的“幕后大厨”:

  • 隐形:你看不到他,但他在厨房里忙活。
  • 重要:他先把基本的食材(核心类)准备好,其他厨师才能做菜。
  • 团队合作:他把食材交给其他厨师(类加载器),大家一起把菜(程序)端上桌。

        从底层看,它是 JVM 用 C/C++ 造出来的“启动助手”;从 Java 代码看,它是个“隐形领队”,通过委托模型指挥其他搬运工。希望这个比喻让读者对引导类加载器有一个清晰的认识!


http://www.kler.cn/a/592418.html

相关文章:

  • 如何理解分布式光纤传感器?
  • 49.71.79.51和49.71.79.42算不算同一个子网中的ip地址吗?
  • Day20:丑数
  • 解码软件需求的三个维度:从满足基础到创造惊喜
  • dart学习记录3(函数)
  • 蓝桥杯备考----》快速幂算法之乘方
  • 大模型开发(六):LoRA项目——新媒体评论智能分类与信息抽取系统
  • 力扣100二刷——图论、回溯
  • electron框架(1.0)认识electron和基础创建
  • 使用PyMongo操作MongoDB(一)
  • MR-Flink-Spark任务提交-常用命令
  • 物联网的数据传输与处理!
  • [GHCTF 2025]真会布置栈吗?
  • WebGL学习2
  • 【红黑树】—— 我与C++的不解之缘(二十五)
  • Windows 图形显示驱动开发-WDDM 3.0功能- 硬件翻转队列(四)
  • K-均值聚类
  • Python 实现高效的实体扩展算法
  • 正点原子[第三期]Arm(iMX6U)Linux移植学习笔记-6.2uboot启动流程-lowlevel_init,s_init,_main函数执行
  • Windows 11右键菜单栏如何修改为Windows 10风格【完整教程】以及如何恢复Win11菜单栏风格