JVM 的启动器类解读 -- sun.misc.Launcher
原文:CSDN(历史文章导入,当前状态为草稿)
Launcher 类解读
Section titled “Launcher 类解读”sun.misc.Launcher 类解读
Section titled “sun.misc.Launcher 类解读”前置知识铺垫
Section titled “前置知识铺垫”什么是类加载器(ClassLoader)
Section titled “什么是类加载器(ClassLoader)”在深入理解 Launcher 之前,我们需要先了解 Java 的类加载机制:
类加载的过程
Section titled “类加载的过程”加载(Loading) ↓链接(Linking) ├─ 验证(Verification) ├─ 准备(Preparation) └─ 解析(Resolution) ↓初始化(Initialization)类加载器的职责
Section titled “类加载器的职责”- 负责将
.class字节码文件加载到 JVM 内存 - 将字节码转换为
java.lang.Class对象 - 通过全限定类名来定位并加载类
双亲委派模型(Parent Delegation Model)
Section titled “双亲委派模型(Parent Delegation Model)”这是理解 Launcher 的核心前置知识:
BootstrapClassLoader(启动类加载器)C++ 实现 ↑ 父加载器 |ExtClassLoader(扩展类加载器) ↑ 父加载器 |AppClassLoader(应用程序类加载器) ↑ 父加载器 |自定义ClassLoader(用户自定义类加载器)双亲委派机制流程
Section titled “双亲委派机制流程”1. 收到类加载请求 ↓2. 先委派给父加载器尝试加载 ↓3. 父加载器找不到? ├─ 是 → 自己尝试加载 └─ 否 → 返回父加载器加载的类为什么需要双亲委派?
Section titled “为什么需要双亲委派?”保证 Java 核心类库的安全性
// 假设你自己写了一个 java.lang.String 类package java.lang;
public class String { // 恶意代码 public void maliciousMethod() { // 危险操作 }}由于双亲委派机制:
- 你的应用要加载
java.lang.String - AppClassLoader 委派给 ExtClassLoader
- ExtClassLoader 委派给 BootstrapClassLoader
- BootstrapClassLoader 已经加载了 JDK 核心的
String类 - 返回 JDK 的
String,你的恶意类永远不会被加载
三大核心类加载器
Section titled “三大核心类加载器”| 类加载器 | 实现语言 | 负责加载的路径 | 父加载器 |
|---|---|---|---|
| BootstrapClassLoader | C++ | $JAVA_HOME/jre/lib (如 rt.jar) |
无 |
| ExtClassLoader | Java | $JAVA_HOME/jre/lib/ext |
BootstrapClassLoader |
| AppClassLoader | Java | CLASSPATH / java.class.path | ExtClassLoader |
JVM 启动流程(简化版)
Section titled “JVM 启动流程(简化版)”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 方法,程序开始运行Launcher 类的核心作用
Section titled “Launcher 类的核心作用”sun.misc.Launcher 是 JVM 启动应用程序的入口类,它的核心职责是:
- 创建并初始化扩展类加载器(ExtClassLoader)
- 创建并初始化应用程序类加载器(AppClassLoader)
- 设置线程上下文类加载器
- 管理安全管理器(SecurityManager)
- 提供访问 Bootstrap ClassPath 的能力
核心设计模式
Section titled “核心设计模式”这个类里面我们可以学习到很多设计模式的使用,整体看下来感觉挺受用的.
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; // ... }}核心源码详细解读
Section titled “核心源码详细解读”Launcher 的核心字段
Section titled “Launcher 的核心字段”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; }}构造函数 - 核心初始化流程
Section titled “构造函数 - 核心初始化流程”这是整个类加载体系的初始化入口:
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); } }}这里需要注意两点!!!
-
为什么先创建 ExtClassLoader?
- 因为 AppClassLoader 需要 ExtClassLoader 作为父加载器
- 这样才能实现双亲委派模型
-
为什么要设置线程上下文类加载器?
Thread.currentThread().setContextClassLoader(loader);这是为了解决 双亲委派模型的局限性:
- 问题场景:BootstrapClassLoader 加载的 JDBC 接口(
java.sql.Driver) - 需要加载:AppClassLoader 路径下的驱动实现类(如
com.mysql.cj.jdbc.Driver) - 矛盾:父类加载器无法访问子类加载器加载的类
- 解决方案:通过线程上下文类加载器(AppClassLoader)实现反向类加载
- 问题场景:BootstrapClassLoader 加载的 JDBC 接口(
ExtClassLoader - 扩展类加载器
Section titled “ExtClassLoader - 扩展类加载器”类定义和继承关系
Section titled “类定义和继承关系”static class ExtClassLoader extends URLClassLoader {
static { // 注册为支持并行加载的类加载器 // 这样多个线程可以同时使用此加载器加载不同的类 ClassLoader.registerAsParallelCapable(); }
// ...}创建 ExtClassLoader
Section titled “创建 ExtClassLoader”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(); }}获取扩展目录
Section titled “获取扩展目录”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;}构造器 - 注意父加载器为 null
Section titled “构造器 - 注意父加载器为 null”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);}将目录转换为 URL 数组
Section titled “将目录转换为 URL 数组”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;}AppClassLoader - 应用程序类加载器
Section titled “AppClassLoader - 应用程序类加载器”static class AppClassLoader extends URLClassLoader {
static { // 支持并行类加载 ClassLoader.registerAsParallelCapable(); }
// URLClassPath 用于高效查找类文件 final URLClassPath ucp;
// ...}创建 AppClassLoader
Section titled “创建 AppClassLoader”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);}重写 loadClass - 性能优化
Section titled “重写 loadClass - 性能优化”这是一个非常巧妙的性能优化:
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 保证线程安全)
路径转 URL 数组
Section titled “路径转 URL 数组”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;}解析类路径字符串
Section titled “解析类路径字符串”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;}文件转 URL
Section titled “文件转 URL”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); }}Factory - URL 协议处理器工厂
Section titled “Factory - URL 协议处理器工厂”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); } }}支持的协议示例:
http→sun.net.www.protocol.http.Handlerhttps→sun.net.www.protocol.https.Handlerfile→sun.net.www.protocol.file.Handlerjar→sun.net.www.protocol.jar.Handlerftp→sun.net.www.protocol.ftp.Handler
PathPermissions - 路径权限管理
Section titled “PathPermissions - 路径权限管理”这个类用于管理类加载器的文件访问权限:
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); }}上下游关系图解
Section titled “上下游关系图解”JVM 启动时的类加载器创建流程
Section titled “JVM 启动时的类加载器创建流程”┌─────────────────────────────────────────────────────────────┐│ 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) │└─────────────────────────────────────────────────────────────┘类加载器的层次结构
Section titled “类加载器的层次结构”┌────────────────────────────────────────────────────────────────┐│ 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 模块化 │└────────────────────────────────────────────────────────────────┘双亲委派模型的类加载流程
Section titled “双亲委派模型的类加载流程”用户调用: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) └─> 用户程序开始运行核心知识点总结
Section titled “核心知识点总结”Launcher 的五大职责
Section titled “Launcher 的五大职责”| 职责 | 说明 | 代码位置 |
|---|---|---|
| 创建 ExtClassLoader | 负责加载扩展目录的 jar 包 | Launcher:72 |
| 创建 AppClassLoader | 负责加载 CLASSPATH 的类 | Launcher:81 |
| 设置上下文类加载器 | 为 SPI 机制提供支持 | Launcher:88 |
| 管理安全管理器 | 可选的安全检查机制 | Launcher:91-111 |
| 提供 Bootstrap ClassPath | 通过 BootClassPathHolder 提供访问 | Launcher:418 |
三大类加载器对比
Section titled “三大类加载器对比”| 特性 | 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 |
5.3 关键设计模式
Section titled “5.3 关键设计模式”-
单例模式
private static Launcher launcher = new Launcher(); -
工厂模式
private static URLStreamHandlerFactory factory = new Factory(); -
懒加载模式
private static class BootClassPathHolder {static final URLClassPath bcp;// 只在第一次调用时初始化} -
双亲委派模式
// 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;}
线程上下文类加载器的作用
Section titled “线程上下文类加载器的作用”问题场景:
BootstrapClassLoader (加载 java.sql.Driver 接口) ↓ 需要加载AppClassLoader (加载 com.mysql.cj.jdbc.Driver 实现类)矛盾: 父类加载器无法访问子类加载器加载的类(违反双亲委派)
解决方案: 线程上下文类加载器
// Launcher.java:88Thread.currentThread().setContextClassLoader(loader);
// ServiceLoader.java:460ClassLoader cl = Thread.currentThread().getContextClassLoader();这样,BootstrapClassLoader 加载的 DriverManager 类就可以通过线程上下文类加载器(AppClassLoader)来加载 JDBC 驱动实现类。
性能优化技巧
Section titled “性能优化技巧”knownToNotExist 缓存
Section titled “knownToNotExist 缓存”// AppClassLoader.java:317if (ucp.knownToNotExist(name)) { // 跳过双亲委派流程,直接抛出异常 throw new ClassNotFoundException(name);}优化效果:
- 避免重复的父加载器查找
- 减少文件系统 I/O 操作
- 对于已知不存在的类,立即失败
MetaIndex 目录索引
Section titled “MetaIndex 目录索引”// ExtClassLoader.java:148MetaIndex.registerDirectory(dirs[i]);优化效果:
- 预先建立目录索引
- 快速判断类是否存在于某个 jar 包
- 避免不必要的 jar 文件扫描
static { ClassLoader.registerAsParallelCapable();}优化效果:
- 允许多线程同时加载不同的类
- 提高多核 CPU 利用率
- 减少类加载时的锁竞争
实际应用场景
Section titled “实际应用场景”JDBC 驱动加载(SPI 机制)
Section titled “JDBC 驱动加载(SPI 机制)”// 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 类加载体系的核心,它的主要价值在于:
-
建立类加载器层次结构
- 创建 ExtClassLoader 和 AppClassLoader
- 实现双亲委派模型
-
为 SPI 机制提供支持
- 设置线程上下文类加载器
- 允许父类加载器访问子类加载器资源
-
优化类加载性能
- MetaIndex 索引机制
- knownToNotExist 缓存
- 并行类加载支持
-
安全机制
- SecurityManager 管理
- 权限控制(PathPermissions)