返回知识工坊

Learning Path

JDK、JRE、JVM 核心关系与运行机制

从 JDK、JRE、JVM 的包含关系切入,深入解析 JVM 跨平台原理、运行模式、类加载核心阶段,以及解释器与 JIT Compiler 协作的执行引擎。

进阶5 张卡25 分钟发布于 2026年7月3日

路径目标

JDK、JRE、JVM 核心关系与运行机制

本学习路径围绕 Java 虚拟机的底层运行展开,帮助读者理清 JDK、JRE、JVM 的层次关系,掌握 JVM 的跨平台部署机制,辨析 Server 与 Client 模式参数差异,并拆解类加载子系统与执行引擎的底层细节,为后续的 JVM 调优和故障排查奠定坚实基础。

5 张知识卡5 个诊断问题5 个边界答案5 个记忆锚点5 个衍生拓展
01
外部资料

说清 JDK、JRE、JVM 的包含关系与核心职责

JDK 是 Java 开发工具包,包含 JRE 和编译器等开发工具;JRE 是 Java 运行环境,包含 JVM 和核心类库;JVM 是 Java 虚拟机,负责将 .class 字节码文件翻译成当前操作系统能执行的机器码。三者是包含关系,JVM 屏蔽了底层系统差异,是跨平台的核心。

诊断题

为什么说把 Java 单纯称为'编译型语言'或'解释型语言'都不准确?在代码从编写到执行的过程中,JVM 扮演了什么核心角色?

答案骨架

我能解释三者的包含关系及运行机制

  1. JDK 包含 JRE 和开发工具(如 javac
  2. JRE 包含 JVM 和核心类库(如 rt.jar
  3. Java 代码先通过编译器编译为与平台无关的 .class 字节码
  4. JVM 负责在运行时将字节码解释/编译为当前平台的机器指令
  5. 因此 JVM 屏蔽了操作系统差异,是实现'一次编写,到处运行'的核心关键。

边界追问

如果服务器上只部署了编译好的 .class 字节码文件而不安装 JDK,仅安装 JRE 足够保证 Java 程序的正常运行吗?

边界答案

完全足够。因为 JRE 内部已经包含了运行 Java 程序所需的 JVM 及核心基础运行类库。只有在需要进行 Java 源代码的编写和编译(使用 javac)时,才必须安装完整的 JDK。生产环境部署通常仅需 JRE

记忆锚点

JDK 开发,JRE 运行,JVM 跨平台;包含关系层层递进。

衍生拓展

  • 了解 JDK 9 引入的模块化(JPMS)系统对 JRE 目录结构(如 rt.jar 消失)的颠覆
  • 探讨不同操作系统下 JVM 动态链接库的文件后缀差异
  • 学习如何使用 jdeps 工具精简并创建自定义 JRE

落地场景

在生产服务器上检查 Java 运行环境的目录结构,确认运行时所需组件的物理位置:

Bash
1# 查看完整 JDK 下的 JRE 目录结构
2ls jdk1.8.0_45/
3# bin(开发工具如 javac) lib(类库) jre(运行环境)
4ls jdk1.8.0_45/jre/
5# bin(包含jvm动态库) lib(核心运行类库)
6
7可见 `JVM` 的可执行文件或动态链接库通常就在 `jre/bin/server``jre/bin/client` 目录下。
打开资料
02
外部资料

解释 JVM 跨平台原理与物理文件路径

JVM 是一种虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现跨平台。它本身并不跨平台,而是为不同平台提供了对应的 JVM 实现。例如在 Windows 下,JVM 核心实现位于 C:\Program Files\Java\jdk1.8.0_45\jre\bin\server,由它负责执行 .class 文件。

诊断题

既然 Java 实现了'Write Once Run Anywhere'(一次编写到处运行),那么 JVM 本身是不是跨平台的?不同操作系统上的 JVM 实现有何根本不同?

答案骨架

我能说清 JVM 的跨平台机制

  1. Java 程序编译后生成与平台无关的字节码 .class 文件
  2. '跨平台'是针对字节码而言的,而 JVM 本身是用 C/C++ 编写的本地应用,它是不跨平台的
  3. 针对不同的操作系统,需要安装对应版本的 JVM(如 Windows 版或 Linux 版)
  4. 各个平台的 JVM 负责将相同的 .class 字节码翻译成对应操作系统的底层机器指令。

边界追问

如果你把 Windows 系统上的 JVM 核心动态链接库文件直接拷贝到 Linux 环境下加载,能顺利执行相同的 .class 文件吗?

边界答案

绝对不能。.class 字节码虽然是平台无关的,但 JVM 本身是由 C/C++ 编写并依赖于特定操作系统 API 的本地应用程序,与底层平台强绑定。因此,必须使用为目标操作系统和 CPU 架构专门编译的特定 JVM 实现。

记忆锚点

字节码跨平台,JVM 不跨平台;不同平台装专属 JVM

衍生拓展

  • 探索 HotSpot JVM 与其他开源虚拟机(如 OpenJ9, GraalVM)的技术差异
  • 深入了解 class 文件格式的二进制结构规范
  • 研究字节码反汇编工具 javap 的用法及输出结构

落地场景

在本地机器上查找并确认 JVM 的核心动态库文件: bash # 进入 Java 安装目录的 server 文件夹 cd C:\Program Files\Java\jdk1.8.0_45\jre\bin\server # 你会找到 JVM 的核心可执行库 # 在 Windows 环境下通常是: jvm.dll # 在 Linux 环境下通常是: libjvm.so

打开资料
03
外部资料

对比 JVM Server 模式与 Client 模式的参数差异

JVM 主要有 ServerClient 两种运行模式。Server 模式专为长期运行的服务器端应用设计,启动较慢但运行时性能和内存管理效率极高,默认堆内存初始为 Xms128M,最大为 Xmx1024MClient 模式启动快,适合桌面应用,默认初始为 Xms1M,最大为 Xmx64M

诊断题

在现代 64 位 JDK 环境下,系统默认会强制选择哪种 JVM 运行模式?如果你的应用是短时间运行的小工具脚本,默认参数会有什么影响?

答案骨架

我能对比两种模式的差异及底层策略

  1. Server 模式默认堆内存 Xms128MXmx1024M,采用更激进的热点代码优化策略,旨在牺牲启动时间换取长期运行的极高吞吐量
  2. Client 模式默认 Xms1MXmx64M,启动快内存小,适合 Swing 等 GUI 桌面应用
  3. 现代 64 位 JDK 移除了 64 位的 Client VM,默认强制使用 Server 模式
  4. 它们通过配置文件 jvm.cfg 控制默认行为。

边界追问

如果在仅安装了 64 位 JDK 的服务器上,强制通过命令行参数 -client 来运行 Java 程序,JVM 会抛出异常报错吗?

边界答案

不会报错,但会忽略该参数。现代 64 位 JDK 为了追求极致的性能,已经不再提供 64 位的 Client VM 实现。在配置文件中标记为 client IGNORE。因此若传递 -client 参数,JVM 会静默忽略,依然以默认的 Server 模式启动。

记忆锚点

Server 重性能慢起步,Client 求极速小内存;64位默认仅 Server。

衍生拓展

  • 研究 Tiered Compilation(分层编译)如何在启动速度与运行性能之间取得平衡
  • 学习 Graal 编译器作为新一代 JIT 替代方案的优劣势
  • 探讨不同的垃圾回收器在不同 JVM 模式下的自适应策略调整

落地场景

查看 JDK 安装目录下的 jvm.cfg 配置文件,了解默认模式优先级: text # 文件路径位于:jre/lib/amd64/jvm.cfg -server KNOWN -client IGNORE -hotspot ALIASED_TO -server # 这表明当前 JDK 只支持 Server 模式,如果遇到 -client 参数会直接忽略(IGNORE)

打开资料
04
外部资料

拆解 Class Loader 类加载机制的核心阶段

Class Loader(类装载器)是 JVM 的核心子系统,其主要功能包含三个阶段:loading(加载,从文件系统或网络读取 .class 字节流);linking(链接,包含验证、准备、解析);initialization(初始化,执行类构造器 <clinit> 方法,为静态变量赋予实际初始值)。

诊断题

linking(链接)阶段包含了验证、准备和解析三个子步骤,其中'解析'阶段把符号引用转为直接引用,这个动作一定会在类加载初期立即完成吗?为什么?

答案骨架

我能描述类加载子系统的三个核心阶段及职责

  1. loading:通过类的全限定名获取定义此类的二进制字节流并生成 Class 对象
  2. linking:包含验证格式、为静态域分配内存并设置默认零值(准备)、将常量池内的符号引用替换为内存地址的直接引用(解析)
  3. initialization:执行类中定义的静态初始化器和静态代码块。initialization 阶段负责执行静态变量的赋值操作。

边界追问

initialization 阶段会执行类构造器 <clinit> 方法。假设一个类中只包含一个被 static final 修饰的常量,这个 <clinit> 方法还会被生成并在初始化阶段触发调用吗?

边界答案

不会生成。如果类中只包含编译期常量(被 static final 修饰且在编译期就能确定值的基本类型或字符串字面量),这类常量会在 linking 的准备阶段直接存入方法区的常量池中,并在使用时直接内联。因此,初始化阶段不需要为它们生成 <clinit> 方法,也不会触发该类的初始化动作。

记忆锚点

一装二连三初始化;加载找字节,连接做校验,初始化跑静态。

衍生拓展

  • 深入学习类加载器的双亲委派模型及其被打破的典型场景(如 JDBC SPI 机制)
  • 探究 Tomcat 等容器是如何实现多个 Web 应用之间类加载器隔离的
  • 研究触发类初始化的 6 种'主动引用'与不触发初始化的'被动引用'边界

落地场景

通过 JVM 参数追踪类加载过程中的各个阶段动作与依赖: bash # 使用 -verbose:class 追踪类的加载和链接情况 java -verbose:class com.example.MyApp # 控制台会输出类似以下日志: # [Loaded java.lang.Object from shared objects file] # [Loaded com.example.MyApp from file:/D:/project/classes/]

打开资料
05
外部资料

辨析 Interpreter 与 JIT Compiler 的执行机制

JVM 执行引擎包含 Interpreter(解释器)和 JIT Compiler(即时编译器)。解释器负责将字节码逐条翻译为本地机器指令,启动快但执行慢;JIT Compiler 在运行时将检测到的'热点代码'(Hot Spot)直接编译为高度优化的本地机器码并缓存,从而大幅提升程序在长期运行时的执行效率。

诊断题

既然 JIT Compiler 能够将字节码编译为本地机器码,大幅提升程序的执行效率,为什么 JVM 还要保留解释器?为什么不一开始就将所有代码全部交给 JIT 编译执行?

答案骨架

我能解释 JVM 执行引擎的双层架构与协作机制

  1. Interpreter 通过查找预定义的 JVM 指令映射,将字节码转换为本地机器指令并执行,它启动响应速度极快,但不进行优化
  2. 为了提高执行效率,JIT Compiler(即时编译器)在运行时探测'热点代码',将其序列编译为本地机器码
  3. 解释器保证了程序刚启动时的极速响应,而 JIT 弥补了纯解释执行的性能损耗,两者优势互补。

边界追问

什么是'热点代码'(Hot Spot)?JVM 是通过什么具体机制来判定一段代码是否为热点代码并触发 JIT 即时编译的?

边界答案

JVM 主要使用基于计数器的热点探测方法。它为每个方法(甚至循环块)建立方法调用计数器和回边计数器。当方法的调用次数或循环回边次数超过某个设定的阈值(通常由 -XX:CompileThreshold 参数指定),就会触发 JIT Compiler 编译。

记忆锚点

解释保速度(启动),编译保性能(运行);热点代码触发 JIT。

衍生拓展

  • 学习 C1 (Client) 和 C2 (Server) 两种 JIT 编译器在优化侧重点上的具体差异
  • 探讨逃逸分析和标量替换等 JIT 编译器的高级优化技术
  • 了解 AOT(Ahead-Of-Time)提前编译技术(如 GraalVM)在现代云原生 Java 中的应用

落地场景

查看并修改 JIT 编译器相关的核心阈值参数,对比性能表现:

Bash
1# 查看当前 JIT 编译阈值
2java -XX:+PrintFlagsFinal -version | grep CompileThreshold
3# 输出示例: intx CompileThreshold = 10000
4# 可以强制关闭 JIT,仅使用解释器来测试启动和执行差异
5java -Xint com.example.MyApp
打开资料
JDK、JRE、JVM 核心关系与运行机制 | 博击长空