【JVM原理详解】04-JDK与JRE与JVM关系辨析

【JVM原理详解】04-JDK与JRE与JVM关系辨析 JDK与JRE与JVM关系辨析引言“装JDK还是JRE”“JDK和JVM到底什么关系”“为什么JDK 9之后没有单独的JRE下载了”——这些问题困扰着许多从入门到进阶的Java开发者。前三篇文章你已经了解了JVM如何执行字节码、发展出多样的实现以及内部三大子系统的分工。本篇将从工具链和部署环境的视角帮你彻底理清JDK、JRE、JVM三者的包含关系和职责分工同时深入探讨JDK 9模块化带来的革命性变化以及在实际工作中如何选择合适的JDK发行版。JDK、JRE、JVM的包含关系三者的关系可以用一句话概括JDK包含JREJRE包含JVM。但这个表述过于简略容易造成理解偏差。更精确的描述是┌─────────────────────────────────────────────────────────┐ │ JDK │ │ (Java Development Kit - Java开发工具包) │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ 开发工具 (Development Tools) │ │ │ │ javac, java, javap, javadoc, jar, │ │ │ │ jdb, jconsole, jshell (≥9), jlink (≥9) │ │ │ │ jstat, jmap, jstack, jcmd, jinfo │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────▼──────────────────────┐ │ │ │ JRE │ │ │ │ (Java Runtime Environment - 运行时环境) │ │ │ │ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ 运行时库 (Runtime Libraries) │ │ │ │ │ │ rt.jar / java.base 等模块 │ │ │ │ │ │ Java核心类库、资源文件 │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ┌───────────────────▼─────────────────┐ │ │ │ │ │ JVM │ │ │ │ │ │ (Java Virtual Machine - 虚拟机) │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ │ │ 类加载器 │ 运行时数据区 │ │ │ │ │ │ │ │ │ 执行引擎 GC │ │ │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ 其他运行时组件 │ │ │ │ │ │ 平台库(如字体、音频) │ │ │ │ │ │ 部署技术(Java Web Start等已弃用) │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ 辅助资源 (Auxiliary Resources) │ │ │ │ include/ (C头文件用于JNI) │ │ │ │ jmods/ (JDK模块文件, ≥9) │ │ │ │ legal/ (许可证文件) │ │ │ │ conf/ (JVM和JDK配置) │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘JVM是最底层的基础。它只负责执行字节码——加载类、管理内存、执行指令、回收垃圾。JVM本身不包含Java标准类库。理论上你可以在一个只有JVM的裸机上执行字节码但你会发现连System.out.println都无法使用因为这个类存在于标准库中而不是JVM中。JRE在JVM的基础上增加了Java标准类库如java.lang、java.util、java.io等以及其他运行时需要的资源文件。JRE是可以运行Java程序的最小环境。它的定位是部署Java应用程序——用户只需要JRE就能运行你的jar包无需安装JDK。JDK在JRE的基础上进一步增加了开发工具。javac编译器、javap反编译器、javadoc文档生成、jconsole监控工具等都是JDK特有的JRE中并不包含。JDK的定位是开发Java程序所需的全套工具。JDK目录结构详解以下是典型JDK安装目录的剖析以JDK 17为例JDK_HOME/ ├── bin/ # 可执行文件所有开发工具和运行时工具 │ ├── java* # JVM启动器 │ ├── javac* # Java编译器JDK特有 │ ├── javap* # 字节码反编译器JDK特有 │ ├── javadoc* # 文档生成器JDK特有 │ ├── jar* # 打包工具 │ ├── jshell* # 交互式REPLJDK 9 │ ├── jlink* # 自定义运行时镜像JDK 9 │ ├── jpackage* # 平台安装包打包JDK 14 │ ├── jstat*/jmap*/jstack* # 诊断和监控工具 │ └── jdb* # 调试器 │ ├── conf/ # JDK配置JDK 9结构变更 │ ├── net.properties # 网络配置 │ └── security/ # 安全策略和证书 │ └── java.security │ ├── include/ # C/C头文件用于JNI开发 │ ├── jni.h │ ├── jvmti.h │ └── win32/jni_md.h # 平台相关的头文件 │ ├── jmods/ # JDK模块文件JDK 9新增 │ ├── java.base.jmod # 基础模块 │ ├── java.desktop.jmod # 桌面模块 │ ├── java.sql.jmod # SQL模块 │ └── ... │ ├── legal/ # 各模块的许可证信息JDK 9 │ ├── java.base/ │ ├── java.desktop/ │ └── ... │ └── lib/ # JDK内部使用的库 ├── src.zip # Java标准库源码学习利器 ├── jrt-fs.jar # JRT文件系统提供者JDK 9 └── ...值得注意的结构变化JDK 8及之前JRE作为一个完全独立的子目录存在jdk/jre/内部包含自己的bin/和lib/目录其中lib/rt.jar包含了所有Java标准类库。如果你只安装了JRE而非JDK你的安装目录就是JRE本身的目录结构。JDK 9到JDK 10JRE目录仍然存在于JDK中但标准库已经从rt.jar迁移到了模块化格式jmods/目录中的.jmod文件。运行时JVM通过jrt://文件系统协议访问模块化类库。这种中间过渡状态存在的时间很短。JDK 11及之后JRE独立子目录正式消失。JDK目录本身就是运行时环境——运行Java程序所需的模块化类库通过jrt-fs.jar文件系统提供。如果需要部署时创建精简的JRE使用jlink工具从JDK中生成一个自定义运行时镜像。这就是为什么Oracle官网从JDK 11起不再提供独立的JRE下载包。rt.jar时代与Jigsaw模块化变革rt.jar的辉煌与困境在JDK 8及之前Java标准类库被打包成一个巨大的rt.jar文件Runtime JAR大小约60MB包含数万个类。这个文件是所有Java应用的基石——你在代码中使用的String、HashMap、ArrayList等都在这里。rt.jar的问题是单体架构的顽疾任何Java应用都要加载整个rt.jar即使你只是一个Hello World程序所有类库的元数据都要被类加载器扫描一遍内部API的滥用泛滥sun.misc.Unsafe、sun.misc.BASE64Encoder等本应为内部使用的类因为被放在具有包访问控制的公开位置被无数第三方库直接调用使得JDK升级变得异常困难部署体积庞大要为嵌入式设备或Docker镜像打包JRE时无法只选取需要的类库子集Project Jigsaw与模块化系统JDK 9引入的Java平台模块系统JPMS即Project Jigsaw从根本上解决了上述问题。核心改变包括1. JDK自身被模块化rt.jar不再存在取而代之的是约90个标准模块Module。例如java.base → java.lang, java.util, java.io 等核心类 java.sql → JDBC相关类 java.xml → XML处理类 java.desktop → AWT, Swing等GUI类 java.logging → 日志框架java.base是唯一所有模块都隐式依赖的根模块。这种设计意味着一个不依赖GUI的应用在运行时完全不会加载java.desktop模块。2. 强封装机制模块通过module-info.java显式声明哪些包是公开APIexports哪些包是内部实现。sun.misc.Unsafe等内部类默认不再对应用代码可见。这从编译器/运行时层面强制了API边界——不再只是文档上的建议。3. 自定义运行时镜像jlink工具可以根据应用的模块依赖生成一个只包含所需模块的精简运行时镜像# 为你的应用创建一个最小化的运行时jlink --module-path$JAVA_HOME/jmods\--add-modules java.base,java.sql\--outputmy-minimal-runtime\--strip-debug--compress2生成的精简运行时通常只有30-50MB相比完整JRE的200MB大幅减小。这在Docker镜像构建中尤为有价值——更小的镜像意味着更快的部署和更低的存储成本。核心工具详解java - JVM启动器Java应用世界的万能入口。它的启动逻辑可以简化为加载JVM动态库jvm.dll/libjvm.so创建JVM实例并设置启动参数通过类加载器找到主类-jar指定jar包或直接指定类名调用主类的main(String[])方法关键选项java-Xms512m-Xmx2g-XX:UseG1GC-jarapp.jar# 设置堆大小和GCjava-cplib/*:myapp.jar com.example.Main# 手动指定classpathjava--add-opens java.base/java.langALL-UNNAMED# JDK 9: 开放封装-jarapp.jarjavac - Java编译器将.java源文件编译为.class字节码文件。关键选项javac-dout/ src/com/example/*.java# 指定输出目录javac--release8Main.java# 兼容JDK 8同时控制source/target和APIjavac-parametersMain.java# 保留方法参数名JDK 8javac-g:noneMain.java# 不生成调试信息javap - 字节码反编译器学习JVM内部机制的利器。常用选项javap-cMain.class# 反编译方法字节码javap-verboseMain.class# 显示完整信息常量池、属性等javap-pMain.class# 包含私有成员javap-sysinfoMain.class# 显示类文件版本信息jlink - 运行时镜像构建JDK 9jlink --module-path jmods\--add-modules java.base,java.logging\--outputmyjrejpackage - 平台安装包生成JDK 14jpackage--inputtarget/--nameMyApp\--main-jar myapp.jar --main-class com.example.Main实践如何选择JDK发行版市场上JDK发行版的选择令人眼花缭乱以下是基于实际使用经验的建议框架选择决策树是否必须Oracle商业支持且预算允许 ├── 是 → Oracle JDK (LTS版本付费订阅获得补丁) └── 否 → 使用OpenJDK衍生发行版 ├── 通用场景 → Adoptium (Eclipse Temurin) │ 特点Eclipse基金会背书TCK合规覆盖面最广 │ ├── 国内大规模部署 → 阿里Dragonwell │ 特点JWarmup预热、多租户GC、阿里大规模验证 │ ├── 云原生微服务 → 考虑OpenJ9 Adoptium │ 特点启动快、内存低、共享类缓存 │ ├── 多语言/GraalVM → GraalVM Community / Oracle GraalVM │ 特点Native Image Truffle多语言支持 │ └── 嵌入式/IoT → Liberica JDK (BellSoft) 特点ARM/MIPS等小众架构支持广版本选择的核心决策因素建议稳定生产JDK 17 LTS 或 JDK 21 LTS存量系统JDK 8但需制定迁移计划前沿特性最新LTS版本容器化部署JDK 17更好的容器感知Spring Boot 3JDK 17起强依赖要求几个常见误区澄清误区一“Oracle JDK才是官方JDK”。实际上所有TCK合规的OpenJDK发行版在执行Java程序的功能上是等效的。Oracle JDK与其他发行版共享同一套OpenJDK源码差异主要在于许可证、打包格式和支持服务。误区二“JDK 8最稳定新版本有未知问题”。JDK 8发布至今超过10年而JDK 17和21已经过了大规模生产验证。新版本在GC性能、容器感知、安全补丁方面有显著优势。固守JDK 8的唯一合理理由是应用依赖的框架/中间件不兼容更高版本而这本身就是一个需要解决的工程债务。误区三“安装JDK就是安装了JVM”。严格来说是安装了包含JVM的完整开发环境。如果你在服务器上只部署应用安装的是JRE或者通过jlink生成的精简运行时而非完整的JDK。实践要点生产服务器不应安装完整JDK安全意识上生产服务器应遵循最小权限原则。JDK中包含jmap、jstack等工具在攻击者获得shell访问后可能被利用。理想的部署方案是通过jlink为应用定制精简运行时既减小体积又减少攻击面。Docker镜像中的JDK选择推荐使用eclipse-temurin:17-jre或对应的-jre-alpine作为基础镜像。如果需要诊断工具使用-jdk版本但在生产环境通过配置禁用远程调试和JMX。多阶段构建中可以仅在构建阶段使用JDK完整镜像运行阶段切换为JRE精简镜像。模块化迁移不是必须的JDK 9的模块化系统对应用代码是可选的——你的应用即使没有module-info.java仍然可以正常编译和运行。只有当你的库面向广泛的开发者公开分发时模块化封装才具有较高的价值。对于内部应用模块化迁移的投入产出比需要仔细评估。rt.jar消失的影响如果你维护的代码中直接依赖了rt.jar的路径如某些老旧的构建脚本或类加载器实现在JDK 9上需要适配。解决方案是使用-Xbootclasspath/a不推荐逐版本变化大或直接使用标准API。javap是学习JVM的窗口在技术成长过程中javap -c -verbose是理解编译器行为、字节码指令、常量池结构的最有效工具。建议在遇到语法糖如Lambda、字符串switch、try-with-resources时用javap查看编译器究竟生成了什么字节码这比读100篇博客都管用。小结JDK ⊃ JRE ⊃ JVM 是三者包含关系的经典表述JDK提供开发工具集JRE提供运行时库JVM是字节码执行的核心引擎JDK 9的Jigsaw模块化系统的本质是将单体rt.jar拆分为可控的模块带来了更强的封装和更精简的运行时jlink工具允许创建只包含所需模块的自定义运行时大幅减少部署体积在容器化和微服务场景下价值突出选择JDK发行版时优先考虑TCK合规的OpenJDK发行版Adoptium是稳妥的首选LTS版本是最务实的选择JDK/JRE/JVM的关系辨析看似基础其实构成了所有JVM知识的地基——理解谁提供什么是后续深入调优和排障的前提