JDK17 lib目录结构

bash
jdk-17/
├── bin/ ← 可执行命令(java, javac, jimage, jlink 等)
│
├── conf/ ← 配置文件
│
├── include/ ← JNI 开发头文件
│
├── jmods/ ← ⭐ 模块完整包(.jmod),用于 jlink 构建自定义运行时
│ ├── java.base.jmod
│ ├── java.compiler.jmod
│ ├── java.desktop.jmod
│ ├── jdk.zipfs.jmod
│ └── ...(共约 70+ 个模块)
│
├── legal/ ← 各模块的许可证文件
│
├── lib/ ← ⭐ 运行时核心库
│ ├── classlist ← 启动时需要预加载的类列表
│ ├── jrt-fs.jar ← IDE 用来访问 jrt:/ 文件系统的工具
│ ├── jvm.cfg ← JVM 配置文件
│ ├── modules ← ⭐⭐⭐ 核心:运行时模块镜像(jimage 二进制文件,约 100~150MB),存储所有标准模块的类文件和资源
│ ├── security/ ← 安全策略、证书库
│ ├── server/ ← Server VM 的 libjvm.so/dll
│ └── ... ← 其他动态库
│
└── release ← JDK 版本信息
java
┌────────────────────┬────────────────────────────┐
│ 文件 作用
├────────────────────┼────────────────────────────┤
│ libattach.dylib │ Attach API(动态加载进程)
├────────────────────┼────────────────────────────┤
│ libawt.dylib │ AWT 图形界面本地实现
├────────────────────┼────────────────────────────┤
│ libdt_socket.dylib │ JDWP 调试传输(socket)
├────────────────────┼────────────────────────────┤
│ libextnet.dylib │ 网络扩展
├────────────────────┼────────────────────────────┤
│ libnet.dylib │ 网络 API(InetAddress 等)
├────────────────────┼────────────────────────────┤
│ libnio.dylib │ NIO 本地实现
├────────────────────┼────────────────────────────┤
│ libzip.dylib │ ZIP 文件处理
└────────────────────┴────────────────────────────┘
用途:
- JDK 的 Java 代码需要调用操作系统功能时,通过 JNI 调用这些 .dylib
- 比如网络连接、文件压缩、字体渲染、图形绘制等
## 关键目录详解
1. lib/modules --- 运行时核心
这是一个二进制文件,不是目录
格式为 JIMAGE,JVM 启动时直接内存映射
包含所有标准模块的类文件、资源文件
运行时通过 jrt:/ 协议访问
2. jmods/ --- 模块完整包
每个 .jmod 文件是一个模块的完整包
包含类文件、原生库(.so/.dll)、配置文件、法律文件
用于 jlink 工具构建自定义运行时镜像
JVM 运行时不直接加载 jmods,而是加载优化后的 lib/modules
JDK17中JVM类加载器
- 引导型类加载器(Bootstrap ClassLoader):
java
┌──────────┬────────────────────────────────────────────────────
│ 项目 说明
├──────────┼────────────────────────────────────────────────────
│ 实现 C++ 实现(非 Java 类)
├──────────┼────────────────────────────────────────────────────
│ 父加载器 无
├──────────┼────────────────────────────────────────────────────
│
加载范围 lib/modules(jimage 二进制文件),
JDK 核心类库(java.base、 java.lang、 java.util、java.logging, java.xml, java.sql 等**核心**模块)
├──────────┼────────────────────────────────────────────────────
│ 路径 $JAVA_HOME/lib 和 $JAVA_HOME/jmods/
├──────────┼────────────────────────────────────────────────────
│ 加载方式 JVM 启动时预加载
└──────────┴────────────────────────────────────────────────────
特点:
- 是所有类加载器的"祖先"
- 加载 JVM 运行时必需的类
- 在 Java 代码中表示为 null(不是 Java 对象)
- 平台类加载器(Platform ClassLoader)
java
┌──────────┬──────────────────────────────────────────────────────────────────────┐
│ 项目 说明
├──────────┼──────────────────────────────────────────────────────────────────────┤
│ 实现 Java 实现,类名 jdk.internal.loader.ClassLoaders$PlatformClassLoader
├──────────┼──────────────────────────────────────────────────────────────────────┤
│ 父加载器 引导类加载器
├──────────┼──────────────────────────────────────────────────────────────────────┤
│ 加载范围 **非核心**平台模块类(java.compiler, java.net.http, jdk.zipfs, jdk.jfr 等
│ (JDK 8 中放在 lib/ext 目录) 等)
├──────────┼──────────────────────────────────────────────────────────────────────┤
│ 路径 $JAVA_HOME/jmods(非 java.base 模块)
├──────────┼──────────────────────────────────────────────────────────────────────┤
│ 替代 JDK 8 的扩展类加载器(ExtClassLoader + lib/ext/ 目录)
└──────────┴──────────────────────────────────────────────────────────────────────┘
特点:
- 用于加载 JDK 平台扩展功能
- 通过 --add-modules 添加的模块由它加载
- 应用程序类加载器(Application ClassLoader):加载classpath路径下的类,主要就是自己写的类
java
┌──────────┬─────────────────────────────────────────────────────────────────┐
│ 项目 说明
├──────────┼─────────────────────────────────────────────────────────────────┤
│ 实现 Java 实现,类名 jdk.internal.loader.ClassLoaders$AppClassLoader
├──────────┼─────────────────────────────────────────────────────────────────┤
│ 父加载器 平台类加载器
├──────────┼─────────────────────────────────────────────────────────────────┤
│ 加载范围 -classpath / -cp / --module-path 指定的路径
├──────────┼─────────────────────────────────────────────────────────────────┤
│ 对应输出目录 用户项目编译输出目录(target/classes/)
│ 第三方依赖 jar(target/dependency/*.jar)
│ 用户自定义模块路径
└──────────┴─────────────────────────────────────────────────────────────────┘
特点:
- 默认的类加载器(ClassLoader.getSystemClassLoader())
- 加载项目代码和第三方依赖(如 jakarta.persistence-api)
- 自定义类加载器:用于加载 动态生成的类、网络加载的类、热部署的类等
java
用户通过继承 ClassLoader 实现,常用于:
- Tomcat 的 WebAppClassLoader(实现应用隔离):
不同 Web 应用依赖不同版本的 Spring/Log4j,如果共用同一个 ClassLoader 会冲突;每个 Web 应用有自己的 WebAppClassLoader,优先加载自己的 WEB-INF/lib 下的 jar,找不到再委派给父加载器。
- Spring Boot 的 DevRestartClassLoader(热重启):不停机更新代码
JVM类加载机制
作用 :规定了类加载的委派顺序和规则
核心规则:当一个类加载器收到加载请求时,它首先不会自己去加载,而是把请求委派给父类去加载,只有当父类加载器无法加载时,子类加载器才会去尝试自己加载。
流程图
bash
自定义 ClassLoader
│
▼ 委派
Application ClassLoader
│
▼ 委派
Platform ClassLoader
│
▼ 委派
Bootstrap ClassLoader
│
▼ 找不到?返回
Application ClassLoader ← 自己尝试加载
│
▼ 找不到?返回
自定义 ClassLoader ← 自己尝试加载
这里是从【自定义 ClassLoader】一直逐层向上委派到最顶层的父类加载器BootStapt ClassLoader,关键点: 每一层都会重复"先查缓存 → 委派给父 → 父找不到自己找"的完整逻辑,所以最终一定会问到 Bootstrap。
为什么要设计双亲委派机制
- 避免类的重复加载
bash
场景:用户自定义了一个 java.lang.String
如果没有双亲委派:
Application ClassLoader 加载了自定义的 String
Bootstrap ClassLoader 也加载了 JDK 的 String
内存中存在两个 String 类 → 类型转换异常、混乱
有双亲委派:
加载请求先委派到 Bootstrap
Bootstrap 在 rt.jar 中找到 String,直接加载
子加载器不再加载
内存中只有一个 String 类
- 保证核心类库的安全性
bash
场景:黑客自定义一个恶意的 java.lang.System
如果没有双亲委派:
Application ClassLoader 加载恶意 System
替换掉标准输入输出、获取系统权限
有双亲委派:
请求先到 Bootstrap
Bootstrap 从 lib/modules 加载官方的 System
恶意 System 永远无法被加载(除非用反射/Unsafe 绕过)
双亲委派机制的破坏:有些场景需要破坏双亲委派机制
| 场景 | 为什么打破 | 怎么打破 |
|---|---|---|
| Tomcat WebApp | 不同 Web 应用需要不同版本的 Spring | 自定义 WebAppClassLoader,先自己加载,找不到再委派 |
| SPI 机制(JDBC) | java.sql.Driver 在 Bootstrap,com.mysql.Driver 在 App |
使用 Thread.currentThread().getContextClassLoader() |
| OSGi | 模块间需要版本隔离 | 每个 Bundle 有自己的 ClassLoader,复杂的网状委派 |
| 热部署 | 需要重新加载修改后的类 | 每次 new 一个新的 ClassLoader,丢弃旧的 |
tomcat打破双亲委派的示例(Tomcat底层使用的不是双亲委派机制)
正常双亲委派
bash
加载请求:org.springframework.context.ApplicationContext
Bootstrap:lib/modules 中没有 Spring → 找不到
↓ 返回
Platform:lib/modules 中没有 Spring → 找不到
↓ 返回
Application:classpath 中有 spring-context-5.3.jar → 找到,加载
结果: Spring 由 Application ClassLoader 加载。
Tomcat 打破双亲委派(先自己加载)
bash
WebApp A 的 WebAppClassLoader 加载:org.springframework.context.ApplicationContext
WebApp A:WEB-INF/lib/spring-context-4.3.jar 中有 → 直接加载!
↑
注意:不会向上问!父加载器(Common)中的 Spring 5.x 被忽略
结果: WebApp A 用 Spring 4.3,WebApp B 用 Spring 5.2,互不干扰。