跳转到内容

JVM 的启动器类解读 -- sun.misc.Launcher

草稿难度:中级#Java源码#JVM

原文:CSDN(历史文章导入,当前状态为草稿)

在深入理解 Launcher 之前,我们需要先了解 Java 的类加载机制:

加载(Loading)
链接(Linking)
├─ 验证(Verification)
├─ 准备(Preparation)
└─ 解析(Resolution)
初始化(Initialization)
  • 负责将 .class 字节码文件加载到 JVM 内存
  • 将字节码转换为 java.lang.Class 对象
  • 通过全限定类名来定位并加载类

双亲委派模型(Parent Delegation Model)

Section titled “双亲委派模型(Parent Delegation Model)”

这是理解 Launcher 的核心前置知识:

BootstrapClassLoader(启动类加载器)C++ 实现
↑ 父加载器
|
ExtClassLoader(扩展类加载器)
↑ 父加载器
|
AppClassLoader(应用程序类加载器)
↑ 父加载器
|
自定义ClassLoader(用户自定义类加载器)
1. 收到类加载请求
2. 先委派给父加载器尝试加载
3. 父加载器找不到?
├─ 是 → 自己尝试加载
└─ 否 → 返回父加载器加载的类

保证 Java 核心类库的安全性

// 假设你自己写了一个 java.lang.String 类
package java.lang;
public class String {
// 恶意代码
public void maliciousMethod() {
// 危险操作
}
}

由于双亲委派机制:

  1. 你的应用要加载 java.lang.String
  2. AppClassLoader 委派给 ExtClassLoader
  3. ExtClassLoader 委派给 BootstrapClassLoader
  4. BootstrapClassLoader 已经加载了 JDK 核心的 String
  5. 返回 JDK 的 String,你的恶意类永远不会被加载

类加载器 实现语言 负责加载的路径 父加载器
BootstrapClassLoader C++ $JAVA_HOME/jre/lib (如 rt.jar)
ExtClassLoader Java $JAVA_HOME/jre/lib/ext BootstrapClassLoader
AppClassLoader Java CLASSPATH / java.class.path ExtClassLoader

1. 操作系统加载 JVM(C++ 编写)
2. JVM 初始化 BootstrapClassLoader(C++ 实现)
3. BootstrapClassLoader 加载核心类库(rt.jar)
4. 创建 sun.misc.Launcher 实例 ← 我们今天的主角
5. Launcher 创建 ExtClassLoader
6. Launcher 创建 AppClassLoader
7. 设置线程上下文类加载器为 AppClassLoader
8. 使用 AppClassLoader 加载主类(main 方法所在的类)
9. 执行 main 方法,程序开始运行

sun.misc.LauncherJVM 启动应用程序的入口类,它的核心职责是:

  1. 创建并初始化扩展类加载器(ExtClassLoader)
  2. 创建并初始化应用程序类加载器(AppClassLoader)
  3. 设置线程上下文类加载器
  4. 管理安全管理器(SecurityManager)
  5. 提供访问 Bootstrap ClassPath 的能力

这个类里面我们可以学习到很多设计模式的使用,整体看下来感觉挺受用的.

public class Launcher {
// 单例模式 - 全局唯一的 Launcher 实例
private static Launcher launcher = new Launcher();
// 工厂模式 - URLStreamHandlerFactory
private static URLStreamHandlerFactory factory = new Factory();
// 懒加载模式 - BootClassPathHolder 静态内部类
private static class BootClassPathHolder {
static final URLClassPath bcp;
// ...
}
}

public class Launcher {
// URL 协议处理器工厂(如 http、https、file 等协议的处理)
private static URLStreamHandlerFactory factory = new Factory();
// 单例 - JVM 全局唯一的 Launcher 实例
private static Launcher launcher = new Launcher();
// 启动类路径(Bootstrap ClassPath)
// 例如:/usr/lib/jvm/java-8/jre/lib/rt.jar
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
// 应用程序类加载器的引用
private ClassLoader loader;
// 获取全局唯一的 Launcher 实例
public static Launcher getLauncher() {
return launcher;
}
}

这是整个类加载体系的初始化入口:

public Launcher() {
// ===========================================
// 步骤1:创建扩展类加载器(ExtClassLoader)
// ===========================================
ClassLoader extcl;
try {
// 调用静态工厂方法创建扩展类加载器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// ===========================================
// 步骤2:创建应用程序类加载器(AppClassLoader)
// 注意:extcl 作为参数传入,成为 AppClassLoader 的父加载器
// ===========================================
try {
// 传入 extcl 作为父加载器
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// ===========================================
// 步骤3:设置主线程的上下文类加载器
// ===========================================
// 这一步非常关键!为 SPI 机制埋下伏笔
// 后续 ServiceLoader 会通过这个上下文类加载器加载服务实现类
Thread.currentThread().setContextClassLoader(loader);
// ===========================================
// 步骤4:安装安全管理器(如果需要)
// ===========================================
String s = System.getProperty("java.security.manager");
if (s != null) {
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
// 使用默认的安全管理器
sm = new java.lang.SecurityManager();
} else {
// 使用自定义的安全管理器类
try {
// 使用 AppClassLoader 加载自定义 SecurityManager
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}

这里需要注意两点!!!

  1. 为什么先创建 ExtClassLoader?

    • 因为 AppClassLoader 需要 ExtClassLoader 作为父加载器
    • 这样才能实现双亲委派模型
  2. 为什么要设置线程上下文类加载器?

    Thread.currentThread().setContextClassLoader(loader);

    这是为了解决 双亲委派模型的局限性

    • 问题场景:BootstrapClassLoader 加载的 JDBC 接口(java.sql.Driver
    • 需要加载:AppClassLoader 路径下的驱动实现类(如 com.mysql.cj.jdbc.Driver
    • 矛盾:父类加载器无法访问子类加载器加载的类
    • 解决方案:通过线程上下文类加载器(AppClassLoader)实现反向类加载

static class ExtClassLoader extends URLClassLoader {
static {
// 注册为支持并行加载的类加载器
// 这样多个线程可以同时使用此加载器加载不同的类
ClassLoader.registerAsParallelCapable();
}
// ...
}
public static ExtClassLoader getExtClassLoader() throws IOException {
// 1. 获取扩展目录列表
final File[] dirs = getExtDirs();
try {
// 2. 在特权块中创建 ExtClassLoader
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
// 为每个扩展目录注册元数据索引(优化类查找速度)
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
// 创建 ExtClassLoader 实例
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
private static File[] getExtDirs() {
// 从系统属性获取扩展目录路径
// 例如:/usr/lib/jvm/java-8/jre/lib/ext
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
// 使用平台相关的路径分隔符分割多个目录
// Windows: ';' Unix/Linux: ':'
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}
public ExtClassLoader(File[] dirs) throws IOException {
// 【重点】第二个参数 parent = null
// 这意味着 ExtClassLoader 的父加载器是 null
// 但在双亲委派时,会委派给 BootstrapClassLoader(C++ 实现,Java 中表示为 null)
super(getExtURLs(dirs), null, factory);
// 初始化查找缓存(优化性能)
SharedSecrets.getJavaNetAccess().
getURLClassPath(this).initLookupCache(this);
}
private static URL[] getExtURLs(File[] dirs) throws IOException {
Vector<URL> urls = new Vector<URL>();
for (int i = 0; i < dirs.length; i++) {
// 列出扩展目录中的所有文件
String[] files = dirs[i].list();
if (files != null) {
for (int j = 0; j < files.length; j++) {
// 跳过 meta-index 文件
if (!files[j].equals("meta-index")) {
File f = new File(dirs[i], files[j]);
// 将 jar 文件路径转换为 URL
urls.add(getFileURL(f));
}
}
}
}
URL[] ua = new URL[urls.size()];
urls.copyInto(ua);
return ua;
}
public String findLibrary(String name) {
// 将库名转换为平台相关的名称
// 例如:Windows -> xxx.dll, Linux -> libxxx.so
name = System.mapLibraryName(name);
URL[] urls = super.getURLs();
File prevDir = null;
for (int i = 0; i < urls.length; i++) {
// 获取扩展目录
File dir = new File(urls[i].getPath()).getParentFile();
if (dir != null && !dir.equals(prevDir)) {
// 1. 先查找架构相关的子目录(如 amd64、x86)
String arch = VM.getSavedProperty("os.arch");
if (arch != null) {
File file = new File(new File(dir, arch), name);
if (file.exists()) {
return file.getAbsolutePath();
}
}
// 2. 再查找扩展目录本身
File file = new File(dir, name);
if (file.exists()) {
return file.getAbsolutePath();
}
}
prevDir = dir;
}
return null;
}

static class AppClassLoader extends URLClassLoader {
static {
// 支持并行类加载
ClassLoader.registerAsParallelCapable();
}
// URLClassPath 用于高效查找类文件
final URLClassPath ucp;
// ...
}
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException {
// 1. 从系统属性获取 CLASSPATH
// 例如:/home/user/app.jar:/home/user/lib/*
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
// 2. 在特权块中创建 AppClassLoader
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
// 【重点】extcl 作为父加载器传入
return new AppClassLoader(urls, extcl);
}
});
}
AppClassLoader(URL[] urls, ClassLoader parent) {
// 【重点】parent 参数 = ExtClassLoader
// 这样就建立了双亲委派的层次结构
super(urls, parent, factory);
// 获取并初始化 URLClassPath(用于高效查找类)
ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
ucp.initLookupCache(this);
}

这是一个非常巧妙的性能优化:

public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1. 安全检查(如果有 SecurityManager)
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 检查包访问权限
sm.checkPackageAccess(name.substring(0, i));
}
}
// 2. 【核心优化】使用缓存判断类是否已知不存在
if (ucp.knownToNotExist(name)) {
// 这个类在父加载器和本地 URLClassPath 中都不存在
// 检查是否已经被动态定义过
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
// 直接抛出异常,避免走完整的双亲委派流程
throw new ClassNotFoundException(name);
}
// 3. 正常走双亲委派流程
return (super.loadClass(name, resolve));
}

性能优化的原理:

正常流程(没有优化):
ClassNotFoundException
委派给 ExtClassLoader
委派给 BootstrapClassLoader
BootstrapClassLoader 查找失败
ExtClassLoader 查找失败
AppClassLoader 查找失败
抛出 ClassNotFoundException
优化后的流程:
查询 knownToNotExist 缓存
命中缓存,已知不存在
直接抛出 ClassNotFoundException
(跳过所有父加载器的查找过程)
protected PermissionCollection getPermissions(CodeSource codesource) {
PermissionCollection perms = super.getPermissions(codesource);
// 允许从 classpath 加载的所有类调用 System.exit()
perms.add(new RuntimePermission("exitVM"));
return perms;
}
运行时添加类路径(Instrumentation)
Section titled “运行时添加类路径(Instrumentation)”
private void appendToClassPathForInstrumentation(String path) {
assert(Thread.holdsLock(this));
// 将新路径添加到类路径
// 如果路径已存在,addURL 是空操作
// 这个方法供 java.lang.instrument.Instrumentation 使用
super.addURL(getFileURL(new File(path)));
}

BootClassPathHolder - 启动类路径持有者

Section titled “BootClassPathHolder - 启动类路径持有者”

这是一个 懒加载 的静态内部类:

private static class BootClassPathHolder {
static final URLClassPath bcp;
static {
URL[] urls;
if (bootClassPath != null) {
// 在特权块中处理 bootClassPath
urls = AccessController.doPrivileged(
new PrivilegedAction<URL[]>() {
public URL[] run() {
File[] classPath = getClassPath(bootClassPath);
int len = classPath.length;
Set<File> seenDirs = new HashSet<File>();
for (int i = 0; i < len; i++) {
File curEntry = classPath[i];
// 对于 jar 文件,注册其父目录
if (!curEntry.isDirectory()) {
curEntry = curEntry.getParentFile();
}
if (curEntry != null && seenDirs.add(curEntry)) {
// 注册目录以优化查找
MetaIndex.registerDirectory(curEntry);
}
}
return pathToURLs(classPath);
}
}
);
} else {
urls = new URL[0];
}
// 创建 URLClassPath 包装器
bcp = new URLClassPath(urls, factory);
bcp.initLookupCache(null);
}
}
// 公共访问方法
public static URLClassPath getBootstrapClassPath() {
// 第一次调用时才会初始化 BootClassPathHolder
return BootClassPathHolder.bcp;
}

懒加载的好处:

  • 只有在需要访问 BootstrapClassPath 时才初始化
  • 避免不必要的资源消耗
  • 线程安全(静态初始化块由 JVM 保证线程安全)

private static URL[] pathToURLs(File[] path) {
URL[] urls = new URL[path.length];
for (int i = 0; i < path.length; i++) {
urls[i] = getFileURL(path[i]);
}
return urls;
}
private static File[] getClassPath(String cp) {
File[] path;
if (cp != null) {
int count = 0, maxCount = 1;
int pos = 0, lastPos = 0;
// 第一遍:统计路径分隔符的数量
while ((pos = cp.indexOf(File.pathSeparator, lastPos)) != -1) {
maxCount++;
lastPos = pos + 1;
}
path = new File[maxCount];
lastPos = pos = 0;
// 第二遍:分割路径
while ((pos = cp.indexOf(File.pathSeparator, lastPos)) != -1) {
if (pos - lastPos > 0) {
path[count++] = new File(cp.substring(lastPos, pos));
} else {
// 空路径组件转换为 "."(当前目录)
path[count++] = new File(".");
}
lastPos = pos + 1;
}
// 处理最后一个路径组件
if (lastPos < cp.length()) {
path[count++] = new File(cp.substring(lastPos));
} else {
path[count++] = new File(".");
}
// 裁剪数组到实际大小
if (count != maxCount) {
File[] tmp = new File[count];
System.arraycopy(path, 0, tmp, 0, count);
path = tmp;
}
} else {
path = new File[0];
}
return path;
}
static URL getFileURL(File file) {
try {
// 转换为规范路径(解析 . 和 .. 等)
file = file.getCanonicalFile();
} catch (IOException e) {}
try {
// 使用 ParseUtil 将文件路径编码为 URL
// 例如:/home/user/app.jar -> file:/home/user/app.jar
return ParseUtil.fileToEncodedURL(file);
} catch (MalformedURLException e) {
throw new InternalError(e);
}
}

private static class Factory implements URLStreamHandlerFactory {
private static String PREFIX = "sun.net.www.protocol";
public URLStreamHandler createURLStreamHandler(String protocol) {
// 根据协议名称构造处理器类名
// 例如:http -> sun.net.www.protocol.http.Handler
String name = PREFIX + "." + protocol + ".Handler";
try {
Class<?> c = Class.forName(name);
return (URLStreamHandler)c.newInstance();
} catch (ReflectiveOperationException e) {
throw new InternalError("could not load " + protocol +
" system protocol handler", e);
}
}
}

支持的协议示例:

  • httpsun.net.www.protocol.http.Handler
  • httpssun.net.www.protocol.https.Handler
  • filesun.net.www.protocol.file.Handler
  • jarsun.net.www.protocol.jar.Handler
  • ftpsun.net.www.protocol.ftp.Handler

这个类用于管理类加载器的文件访问权限:

class PathPermissions extends PermissionCollection {
private File path[]; // 路径数组
private Permissions perms; // 权限集合
URL codeBase; // 代码源
PathPermissions(File path[]) {
this.path = path;
this.perms = null; // 延迟初始化
this.codeBase = null;
}
private synchronized void init() {
if (perms != null)
return; // 已经初始化过
perms = new Permissions();
// 1. 添加创建类加载器的权限
perms.add(SecurityConstants.CREATE_CLASSLOADER_PERMISSION);
// 2. 添加读取所有 "java.*" 系统属性的权限
perms.add(new java.util.PropertyPermission("java.*",
SecurityConstants.PROPERTY_READ_ACTION));
// 3. 为每个路径添加文件读取权限
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
for (int i = 0; i < path.length; i++) {
File f = path[i];
String path;
try {
path = f.getCanonicalPath();
} catch (IOException ioe) {
path = f.getAbsolutePath();
}
if (i == 0) {
codeBase = Launcher.getFileURL(new File(path));
}
if (f.isDirectory()) {
// 目录:授予递归读取权限(-)
if (path.endsWith(File.separator)) {
perms.add(new FilePermission(path + "-",
SecurityConstants.FILE_READ_ACTION));
} else {
perms.add(new FilePermission(
path + File.separator + "-",
SecurityConstants.FILE_READ_ACTION));
}
} else {
// 文件:授予其所在目录的递归读取权限
int endIndex = path.lastIndexOf(File.separatorChar);
if (endIndex != -1) {
path = path.substring(0, endIndex + 1) + "-";
perms.add(new FilePermission(path,
SecurityConstants.FILE_READ_ACTION));
}
}
}
return null;
}
});
}
public boolean implies(java.security.Permission permission) {
if (perms == null)
init(); // 懒加载
return perms.implies(permission);
}
}

┌─────────────────────────────────────────────────────────────┐
│ JVM 启动(C++ 层) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 创建 BootstrapClassLoader (C++ 实现) │
│ 负责加载:$JAVA_HOME/jre/lib/rt.jar 等核心类库 │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ BootstrapClassLoader 加载 sun.misc.Launcher 类 │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 执行 Launcher 的静态初始化 │
│ private static Launcher launcher = new Launcher(); │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Launcher 构造函数执行 │
└─────────────────┬───────────────────────────────────────────┘
├─────────────────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 步骤1: 创建 │ │ 步骤2: 创建 │
│ ExtClassLoader │ │ AppClassLoader │
│ │ │ │
│ ↓ getExtClassLoader()│ │ ↓ getAppClassLoader()│
│ ↓ getExtDirs() │ │ ↓ getClassPath() │
│ ↓ 读取 java.ext.dirs │ │ ↓ 读取 java.class.path│
│ ↓ new ExtClassLoader │ │ ↓ new AppClassLoader │
│ parent = null │ │ parent = extcl │
└──────────────────────┘ └──────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 步骤3: 设置线程上下文类加载器 │
│ Thread.currentThread().setContextClassLoader(loader); │
│ (loader = AppClassLoader) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 步骤4: 安装 SecurityManager(可选) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 使用 AppClassLoader 加载用户主类 │
│ loader.loadClass("com.example.Main") │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 执行主类的 main 方法 │
│ Main.main(String[] args) │
└─────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────┐
│ BootstrapClassLoader │
│ (C++ 实现, Java 中表示为 null) │
│ │
│ 负责加载: │
│ • $JAVA_HOME/jre/lib/rt.jar │
│ • $JAVA_HOME/jre/lib/resources.jar │
│ • $JAVA_HOME/jre/lib/charsets.jar │
│ • 核心 Java 类库(java.*, javax.*, sun.* 等) │
└────────────────────────┬───────────────────────────────────────┘
│ 父加载器
┌────────────────────────────────────────────────────────────────┐
│ ExtClassLoader │
│ (sun.misc.Launcher$ExtClassLoader) │
│ extends URLClassLoader │
│ │
│ 负责加载: │
│ • $JAVA_HOME/jre/lib/ext/*.jar │
│ • java.ext.dirs 系统属性指定的目录 │
│ 示例: │
│ • /usr/lib/jvm/java-8/jre/lib/ext/cldrdata.jar │
│ • /usr/lib/jvm/java-8/jre/lib/ext/sunec.jar │
└────────────────────────┬───────────────────────────────────────┘
│ 父加载器
┌────────────────────────────────────────────────────────────────┐
│ AppClassLoader │
│ (sun.misc.Launcher$AppClassLoader) │
│ extends URLClassLoader │
│ │
│ 负责加载: │
│ • CLASSPATH 环境变量指定的路径 │
│ • java.class.path 系统属性指定的路径 │
│ • -cp / -classpath 命令行参数指定的路径 │
│ 示例: │
│ • /home/user/myapp.jar │
│ • /home/user/lib/* │
│ • /home/user/classes/ │
└────────────────────────┬───────────────────────────────────────┘
│ 父加载器
┌────────────────────────────────────────────────────────────────┐
│ 自定义 ClassLoader │
│ (用户自定义, extends ClassLoader) │
│ │
│ 用途: │
│ • 加载网络资源 │
│ • 字节码加密/解密 │
│ • 热部署 │
│ • OSGi 模块化 │
└────────────────────────────────────────────────────────────────┘

用户调用:Class.forName("com.example.MyClass")
┌─────────────────────────────────────────────────────────────┐
│ AppClassLoader.loadClass("com.example.MyClass") │
│ │
│ 1. 检查类是否已加载 (findLoadedClass) │
│ └─> 已加载?返回缓存的 Class 对象 │
│ │
│ 2. 委派给父加载器 │
│ └─> parent.loadClass("com.example.MyClass") │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ExtClassLoader.loadClass("com.example.MyClass") │
│ │
│ 1. 检查类是否已加载 (findLoadedClass) │
│ └─> 已加载?返回缓存的 Class 对象 │
│ │
│ 2. 委派给父加载器 │
│ └─> parent.loadClass("com.example.MyClass") │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ BootstrapClassLoader.loadClass("com.example.MyClass") │
│ │
│ 在 $JAVA_HOME/jre/lib/ 中查找 │
│ └─> 找不到 com.example.MyClass │
│ └─> 返回 null │
└─────────────────────────┬───────────────────────────────────┘
│ 父加载器找不到
┌─────────────────────────────────────────────────────────────┐
│ 回到 ExtClassLoader │
│ │
│ 3. 父加载器加载失败,自己尝试加载 │
│ └─> findClass("com.example.MyClass") │
│ └─> 在 $JAVA_HOME/jre/lib/ext/ 中查找 │
│ └─> 找不到 com.example.MyClass │
│ └─> 抛出 ClassNotFoundException │
└─────────────────────────┬───────────────────────────────────┘
│ 自己也找不到
┌─────────────────────────────────────────────────────────────┐
│ 回到 AppClassLoader │
│ │
│ 3. 父加载器加载失败,自己尝试加载 │
│ └─> findClass("com.example.MyClass") │
│ └─> 在 CLASSPATH 中查找 │
│ └─> 找到 /home/user/classes/com/example/MyClass.class │
│ └─> 读取字节码并调用 defineClass │
│ └─> 返回 Class<MyClass> 对象 ✓ │
└─────────────────────────────────────────────────────────────┘

Launcher 与其他关键类的协作关系

Section titled “Launcher 与其他关键类的协作关系”
┌────────────────────────────────────────────────────────────┐
│ JVM (C++ 层) │
└────────┬───────────────────────────────────────────────────┘
│ 调用
┌────────────────────────────────────────────────────────────┐
│ sun.misc.Launcher │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ private static Launcher launcher = new Launcher()│ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ 创建 ┌──────────────────────┐ │
│ ─────> │ ExtClassLoader │ │
│ │ parent = null │ │
│ └──────────┬───────────┘ │
│ │ │
│ 创建 │ 作为父加载器 │
│ ─────> ┌──────────▼───────────┐ │
│ │ AppClassLoader │ │
│ │ parent = extcl │ │
│ └──────────────────────┘ │
└────────────────────┬───────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Thread (主线程) │
│ │
│ contextClassLoader = AppClassLoader │
│ (通过 Thread.currentThread().setContextClassLoader 设置) │
└────────────────────┬───────────────────────────────────────┘
│ 被使用
┌────────────────────────────────────────────────────────────┐
│ ServiceLoader (SPI 机制) │
│ │
│ public static <S> ServiceLoader<S> load(Class<S> service)│
│ { │
│ // 获取线程上下文类加载器(AppClassLoader) │
│ ClassLoader cl = │
│ Thread.currentThread().getContextClassLoader(); │
│ return ServiceLoader.load(service, cl); │
│ } │
└────────────────────────────────────────────────────────────┘
│ 实现双亲委派模型的"破坏"
┌────────────────────────────────────────────────────────────┐
│ DriverManager (JDBC) │
│ │
│ static { │
│ // 在静态初始化块中使用 SPI 加载驱动 │
│ ServiceLoader<Driver> loadedDrivers = │
│ ServiceLoader.load(Driver.class); │
│ // ... │
│ } │
│ │
│ 场景说明: │
│ • DriverManager 在 rt.jar 中,由 BootstrapClassLoader 加载│
│ • MySQL 驱动在 CLASSPATH 中,由 AppClassLoader 加载 │
│ • 通过线程上下文类加载器,父加载器可以访问子加载器资源 │
└────────────────────────────────────────────────────────────┘

类加载过程中的关键方法调用链

Section titled “类加载过程中的关键方法调用链”
JVM 启动
├─> (C++) 创建 BootstrapClassLoader
├─> (C++) 加载 sun.misc.Launcher 类
│ └─> BootstrapClassLoader.loadClass("sun.misc.Launcher")
├─> (Java) 执行 Launcher 静态初始化
│ └─> private static Launcher launcher = new Launcher();
│ │
│ ├─> ExtClassLoader.getExtClassLoader()
│ │ │
│ │ ├─> getExtDirs()
│ │ │ └─> System.getProperty("java.ext.dirs")
│ │ │
│ │ ├─> new ExtClassLoader(dirs)
│ │ │ └─> super(getExtURLs(dirs), null, factory)
│ │ │ └─> URLClassLoader 构造器
│ │ │
│ │ └─> 返回 ExtClassLoader 实例
│ │
│ ├─> AppClassLoader.getAppClassLoader(extcl)
│ │ │
│ │ ├─> System.getProperty("java.class.path")
│ │ │
│ │ ├─> getClassPath(s)
│ │ │ └─> 解析 CLASSPATH 字符串
│ │ │
│ │ ├─> new AppClassLoader(urls, extcl)
│ │ │ └─> super(urls, parent, factory)
│ │ │ └─> URLClassLoader 构造器
│ │ │
│ │ └─> 返回 AppClassLoader 实例
│ │
│ ├─> Thread.currentThread().setContextClassLoader(loader)
│ │ └─> 设置线程上下文类加载器为 AppClassLoader
│ │
│ └─> 安装 SecurityManager(可选)
├─> (C++) 使用 AppClassLoader 加载主类
│ └─> AppClassLoader.loadClass("com.example.Main")
│ │
│ ├─> 检查类是否已加载
│ │ └─> findLoadedClass("com.example.Main")
│ │
│ ├─> 委派给父加载器(双亲委派)
│ │ └─> parent.loadClass("com.example.Main")
│ │ └─> ExtClassLoader.loadClass(...)
│ │ └─> parent.loadClass(...)
│ │ └─> BootstrapClassLoader 查找失败
│ │ └─> 返回 null
│ │
│ ├─> 父加载器加载失败,自己尝试加载
│ │ └─> findClass("com.example.Main")
│ │ │
│ │ ├─> URLClassLoader.findClass(...)
│ │ │ │
│ │ │ ├─> ucp.getResource(...)
│ │ │ │ └─> 在 CLASSPATH 中查找 .class 文件
│ │ │ │
│ │ │ └─> defineClass(...)
│ │ │ └─> 将字节码转换为 Class 对象
│ │ │
│ │ └─> 返回 Class<Main> 对象
│ │
│ └─> 返回 Class<Main> 对象
└─> (C++) 调用主类的 main 方法
└─> Main.main(String[] args)
└─> 用户程序开始运行

职责 说明 代码位置
创建 ExtClassLoader 负责加载扩展目录的 jar 包 Launcher:72
创建 AppClassLoader 负责加载 CLASSPATH 的类 Launcher:81
设置上下文类加载器 为 SPI 机制提供支持 Launcher:88
管理安全管理器 可选的安全检查机制 Launcher:91-111
提供 Bootstrap ClassPath 通过 BootClassPathHolder 提供访问 Launcher:418

特性 BootstrapClassLoader ExtClassLoader AppClassLoader
实现语言 C++ Java Java
父加载器 null (实际指向 Bootstrap) ExtClassLoader
加载路径 $JAVA_HOME/jre/lib $JAVA_HOME/jre/lib/ext CLASSPATH
系统属性 sun.boot.class.path java.ext.dirs java.class.path
典型加载类 java.lang.String javax.crypto.* 用户应用类
Java 表示 null Launcher$ExtClassLoader Launcher$AppClassLoader

  1. 单例模式

    private static Launcher launcher = new Launcher();
  2. 工厂模式

    private static URLStreamHandlerFactory factory = new Factory();
  3. 懒加载模式

    private static class BootClassPathHolder {
    static final URLClassPath bcp;
    // 只在第一次调用时初始化
    }
  4. 双亲委派模式

    // URLClassLoader.loadClass 的默认实现
    protected Class<?> loadClass(String name, boolean resolve) {
    // 1. 检查缓存
    Class<?> c = findLoadedClass(name);
    if (c == null) {
    // 2. 委派给父加载器
    if (parent != null) {
    c = parent.loadClass(name, false);
    }
    // 3. 父加载器加载失败,自己尝试
    if (c == null) {
    c = findClass(name);
    }
    }
    return c;
    }

问题场景:

BootstrapClassLoader (加载 java.sql.Driver 接口)
↓ 需要加载
AppClassLoader (加载 com.mysql.cj.jdbc.Driver 实现类)

矛盾: 父类加载器无法访问子类加载器加载的类(违反双亲委派)

解决方案: 线程上下文类加载器

// Launcher.java:88
Thread.currentThread().setContextClassLoader(loader);
// ServiceLoader.java:460
ClassLoader cl = Thread.currentThread().getContextClassLoader();

这样,BootstrapClassLoader 加载的 DriverManager 类就可以通过线程上下文类加载器(AppClassLoader)来加载 JDBC 驱动实现类。


// AppClassLoader.java:317
if (ucp.knownToNotExist(name)) {
// 跳过双亲委派流程,直接抛出异常
throw new ClassNotFoundException(name);
}

优化效果:

  • 避免重复的父加载器查找
  • 减少文件系统 I/O 操作
  • 对于已知不存在的类,立即失败
// ExtClassLoader.java:148
MetaIndex.registerDirectory(dirs[i]);

优化效果:

  • 预先建立目录索引
  • 快速判断类是否存在于某个 jar 包
  • 避免不必要的 jar 文件扫描
static {
ClassLoader.registerAsParallelCapable();
}

优化效果:

  • 允许多线程同时加载不同的类
  • 提高多核 CPU 利用率
  • 减少类加载时的锁竞争

// DriverManager.java (由 BootstrapClassLoader 加载)
static {
loadInitialDrivers();
}
private static void loadInitialDrivers() {
// 使用 SPI 加载驱动
// 这里会用到线程上下文类加载器(AppClassLoader)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
for (Driver driver : loadedDrivers) {
// 加载 com.mysql.cj.jdbc.Driver 等实现类
}
}

调用链:

BootstrapClassLoader (加载 DriverManager)
└─> ServiceLoader.load(Driver.class)
└─> Thread.currentThread().getContextClassLoader()
└─> 返回 AppClassLoader
└─> 加载 MySQL Driver (在 CLASSPATH 中)

Tomcat 类加载器(自定义类加载器)

Section titled “Tomcat 类加载器(自定义类加载器)”

Tomcat 为每个 Web 应用创建独立的类加载器:

BootstrapClassLoader
ExtClassLoader
AppClassLoader
Common ClassLoader (Tomcat 公共类)
WebApp ClassLoader1 (应用1) | WebApp ClassLoader2 (应用2)

打破双亲委派的原因:

  • 每个 Web 应用隔离(可以有不同版本的库)
  • 热部署(重新加载某个应用而不影响其他应用)

sun.misc.Launcher 是 Java 类加载体系的核心,它的主要价值在于:

  1. 建立类加载器层次结构

    • 创建 ExtClassLoader 和 AppClassLoader
    • 实现双亲委派模型
  2. 为 SPI 机制提供支持

    • 设置线程上下文类加载器
    • 允许父类加载器访问子类加载器资源
  3. 优化类加载性能

    • MetaIndex 索引机制
    • knownToNotExist 缓存
    • 并行类加载支持
  4. 安全机制

    • SecurityManager 管理
    • 权限控制(PathPermissions)