对于Java开发者而言,我们编写的每一行代码,从源文件到最终成为JVM中运行的服务,都离不开类加载器的层层牵引。其中,ExtClassLoader、AppClassLoader以及Bootstrap ClassLoader三者协同工作,将.class文件加载至内存,一步步构建出Java程序的运行骨架。你是否好奇过这段旅程是如何实现的?本系列将通过三篇文章,系统解析Java类加载器的核心机制与实现原理。
首篇我们将聚焦于启动类加载器——Bootstrap ClassLoader。它作为类加载体系的根基,负责加载rt.jar等核心Java类库。值得注意的是,它本身由JVM原生实现,并非一个Java类。今天,我们将深入HotSpot虚拟机的C++源码层,揭开Bootstrap ClassLoader的底层实现逻辑·1理解Java核心类加载的本质支撑。
一、启动类加载器
在深入源码前,我们先明确启动类加载器的 3 个核心特性 —— 这是理解底层实现的关键:
- 是使用c/c++语言实现的, 嵌套在JVM内部。
- 加载java的核心库(java_home/jre/lib/rt.jar/resource.jar或sun.boot.class.path路径下的内容),用于提供jvm自身需要的类。
- 没有父加载器。
启动类加载器的实现集中在 3 个核心功能:管理启动类路径、执行类加载逻辑、加载c++实现。下面我们逐一拆解。
二、启动类加载路径的底层管理:从参数到链表
Java 开发者都知道 “Bootstrap Classpath” 是启动类加载器的 “搜索范围”,但底层如何初始化、解析这个路径?我们从 JVM 启动流程的 C++ 调用链说起。
2.1 启动类路径的初始化:从main到os::set_boot_path
JVM 启动时,会通过一系列 C++ 函数拼接默认的启动类路径,并将其存入全局参数。关键调用链如下(对应源码路径):
jdk\jdk\src\share\bin\main.c:main()
→ jdk\jdk\src\share\bin\java.c:JLI_Launch()
→ jdk\jdk\src\solaris\bin\java_md_solinux.c:JVMInit()
→ jdk\jdk\src\share\bin\java.c:ContinueInNewThread()
→ jdk\jdk\src\share\bin\java.c:JavaMain()
→ jdk\hotspot\src\share\vm\runtime\thread.cpp:create_vm()
→ jdk\hotspot\src\share\vm\runtime\arguments.cpp:Arguments::init_system_properties()
→ jdk\hotspot\src\os\linux\vm\os_linux.cpp:os::init_system_properties_values()
→ jdk\hotspot\src\share\vm\runtime\os.cpp:os::set_boot_path()
终负责拼接路径的核心函数是os::set_boot_path,它会根据JAVA_HOME生成默认的 Bootstrap Classpath,并设置到 JVM 的全局参数中。源码如下:
bool os::set_boot_path(char fileSep, char pathSep) {
const char* home = Arguments::get_java_home();
int home_len = (int)strlen(home);
static const char* meta_index_dir_format = "%/lib/";
static const char* meta_index_format = "%/lib/meta-index";
char* meta_index = format_boot_path(meta_index_format, home, home_len, fileSep, pathSep);
if (meta_index == NULL) return false;
char* meta_index_dir = format_boot_path(meta_index_dir_format, home, home_len, fileSep, pathSep);
if (meta_index_dir == NULL) return false;
Arguments::set_meta_index_path(meta_index, meta_index_dir);
// 拼接默认的Bootstrap Classpath,包含rt.jar、charsets.jar等核心JAR
static const char classpath_format[] =
"%/lib/resources.jar:"
"%/lib/rt.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/lib/jfr.jar:"
#ifdef __APPLE__
"%/lib/JObjC.jar:"
#endif
"%/classes";
char* sysclasspath = format_boot_path(classpath_format, home, home_len, fileSep, pathSep);
if (sysclasspath == NULL) return false;
Arguments::set_sysclasspath(sysclasspath);
return true;
}
关键解读(面向 Java 开发者):
%会被替换为JAVA_HOME路径(如/usr/lib/jvm/jdk1.8.0),最终生成类似/usr/lib/jvm/jdk1.8.0/lib/rt.jar的路径;- 这段代码决定了 “默认情况下启动类加载器能加载哪些 JAR”,也是
-Xbootclasspath参数修改路径的底层入口; - 生成的路径字符串会存入
Arguments(JVM 启动参数管理类)的sysclasspath变量,供后续解析使用。
2.2 启动类路径的解析:从字符串到链表
拼接好的路径字符串(如rt.jar:charsets.jar)无法直接用于类搜索,需要解析成 JVM 内部的 “路径链表”。这个工作由ClassLoader::setup_bootstrap_search_path完成,调用链如下:
src\share\bin\main.c:main()
→ src\share\bin\java.c:JLI_Launch()
→ jdk\jdk\src\solaris\bin\java_md_solinux.c:JVMInit()
→ jdk\jdk\src\share\bin\java.c:ContinueInNewThread()
→ jdk\jdk\src\share\bin\java.c:JavaMain()
→ jdk\jdk\src\share\bin\java.c:LoadMainClass()
→ jdk\jdk\src\share\javavm\export\jni.h:CallStaticObjectMethod()
→ hotspot\src\share\vm\runtime\thread.cpp:create_vm()
→ hotspot\src\share\vm\runtime\init.cpp:init_globals()
→ hotspot\src\share\vm\classfile\classLoader.cpp:classLoader_init()
→ hotspot\src\share\vm\classfile\classLoader.cpp:initialize()
→ hotspot\src\share\vm\classfile\classLoader.cpp:setup_bootstrap_search_path()
源码实现如下:
void ClassLoader::setup_bootstrap_search_path() {
// 确保只初始化一次 bootstrap class path(只能在 JVM 启动初始化阶段调用一次)
assert(_first_entry == NULL, "should not setup bootstrap class search path twice");
// 从启动参数里取得系统 classpath 的 C 字符串副本(例如 -Xbootclasspath 或默认的 bootstrap 路径)
// Arguments::get_sysclasspath() 返回 const char*,os::strdup 会在 C 堆上复制一份返回 char*
// 调用者负责 later 释放这块内存(在本函数中原代码并未显式 free;注意这一点)。
char* sys_class_path = os::strdup(Arguments::get_sysclasspath());
// 如果开启了 TraceClassLoading && Verbose,那么打印 debug 信息到 tty(控制台/日志)
if (TraceClassLoading && Verbose) {
tty->print_cr("[Bootstrap loader class path=%s]", sys_class_path);
}
// 获取字符串长度(不包括终止符号)
int len = (int)strlen(sys_class_path);
// 'end' 用作扫描位置的索引(end 指向下一个待处理字符或分隔符)
int end = 0;
// 遍历 sys_class_path 中由分隔符分隔的每一段路径
// start 从 0 开始,每次循环把 start 移到上一次循环结束的位置(end)
for (int start = 0; start < len; start = end) {
// 将 end 移动到当前路径段的末尾(遇到路径分隔符或字符串末尾就停)
while (sys_class_path[end] && sys_class_path[end] != os::path_separator()[0]) {
end++;
}
// 现在 [start, end) 是一段路径子串,长度为 (end - start)
// 申请一个 C 堆数组用来临时保存这个子串(多分配一个字节用于 '\0')
char* path = NEW_C_HEAP_ARRAY(char, end-start+1, mtClass);
// 复制字节(注意:strncpy 不会自动在目标尾部添加 '\0',所以下面单独设置)
strncpy(path, &sys_class_path[start], end-start);
// 手动添加终止符,保证 path 是以 '\0' 结束的 C 字符串
path[end-start] = '\0';
// 把这个路径条目加入 classpath 的内部链表(bootstrap search path 的数据结构)
// 第二个参数 false 可能表示这是普通条目(不是某些特殊类型)
update_class_path_entry_list(path, false);
// 释放临时 path 缓存(因为 update_class_path_entry_list 已经把必要信息复制/持有)
FREE_C_HEAP_ARRAY(char, path, mtClass);
// 跳过连续的路径分隔符(比如 "a::b" 的情况),保证 start 指向下一个非分隔符位置
while (sys_class_path[end] == os::path_separator()[0]) {
end++;
}
}
}
关键解读(面向 Java 开发者):
- 核心逻辑是 “拆分路径字符串→生成链

1万+

被折叠的 条评论
为什么被折叠?



