1. 概述

本文旨在厘清 JVM、JRE 和 JDK 三者之间的区别,从它们的组成结构和实际用途出发,帮助开发者建立清晰的认知。这三者是 Java 开发生态的基石,理解它们的关系,能让你在部署、调优甚至踩坑时更加得心应手。

简单粗暴地说:

  • JDK 是开发者的工具箱,包含了写代码、编译、调试所需的一切。
  • JRE 是运行环境,普通用户安装它就能跑 Java 程序。
  • JVM 是核心引擎,真正执行字节码的地方。

下面逐个拆解。

2. JVM(Java 虚拟机)

Java 虚拟机(JVM)是执行 Java 程序的虚拟机实现。

它不直接执行 Java 源码,而是加载由 javac 编译生成的字节码(.class 文件),然后解释或编译执行。JVM 是一个抽象的计算机器,拥有自己的指令集,并在运行时管理多个内存区域。

JVM 的主要组件包括:

  • ✅ 类加载器(Class Loaders)
  • ✅ 运行时数据区(Run-Time Data Areas)
  • ✅ 执行引擎(Execution Engine)
  • ✅ Java 原生接口(JNI)
  • ✅ 原生库(Native Libraries)

2.1. 类加载器(Class Loaders)

类加载器负责加载、验证和链接字节码。这是 JVM 启动时的首要任务。它确保类的字节码符合 JVM 规范,防止恶意代码注入。

我们有专门的文章深入探讨 类加载器机制,这里不再赘述。

2.2. 运行时数据区(Run-Time Data Areas)

JVM 定义了多个内存区域用于程序运行。这些区域有的随 JVM 启动而创建,有的则随线程创建而分配。

方法区(Method Area)

方法区用于存储类的元数据,比如:

  • ✅ 运行时常量池(Runtime Constant Pool)
  • ✅ 字段和方法数据
  • ✅ 方法和构造器的字节码
  • ✅ 完全限定的类名

这个区域在 JVM 启动时创建,所有线程共享。在早期版本中,它被称为“永久代”(PermGen),后来被元空间(Metaspace)取代。内存不必连续。

堆区(Heap Area)

堆是所有对象实例和数组的分配区域。垃圾回收器(GC)主要管理的就是堆内存。

堆内存分为三个部分:

  • Eden 区:新生代的一部分。新创建的对象通常分配在此。
  • Survivor 区:也是新生代的一部分。存放经过一次 Minor GC 后仍然存活的对象。
  • Tenured 区(老年代):存放长期存活的对象。当对象在新生代中经历了多次 GC 仍存活,就会被晋升到老年代。

堆区在 JVM 启动时创建,所有线程共享。内存不必连续。

虚拟机栈(Stack Area)

每个线程私有的内存区域,以栈帧(Stack Frame)的形式存储:

  • ✅ 局部变量表(Local Variable Array)
  • ✅ 操作数栈(Operand Stack)——用于存放中间计算结果
  • ✅ 栈帧数据(Frame Data)——包含返回地址、异常表引用等

每个方法调用都会创建一个栈帧,方法执行完毕后出栈。内存不必连续。

程序计数器(PC Registers)

每个线程都有独立的 PC 寄存器,记录当前正在执行的字节码指令地址。如果当前方法是本地方法(native),则此值为 undefined。

本地方法栈(Native Method Stacks)

用于支持 JVM 调用本地方法(如 C/C++ 编写的代码)。它通常被称为“C 栈”。每个线程私有,不共享。

2.3. 执行引擎(Execution Engine)

执行引擎负责执行字节码,主要包含三部分:

解释器(Interpreter)

逐行解释执行字节码。优点是启动快,缺点是执行效率低。频繁调用的方法会被反复解释,性能堪忧。

即时编译器(JIT Compiler)

为了解决解释器的性能瓶颈,JVM 引入了 JIT(Just-In-Time)编译器。

  • ✅ JIT 会监控方法的调用频率。
  • ✅ 当某个方法被频繁调用(称为“热点方法”),JIT 会将其字节码编译为本地机器码。
  • ✅ 编译在后台线程进行,不阻塞主线程。
  • ✅ 编译后的代码执行速度大幅提升。

这就是为什么 Java 程序运行一段时间后性能会“起飞”的原因。

垃圾回收器(Garbage Collector)

GC 负责自动管理堆内存:

  • ✅ 找出不再使用的对象(垃圾)
  • ✅ 回收其占用的内存

GC 是一个守护线程。虽然可以通过 System.gc() 建议 JVM 执行 GC,但具体何时执行由 JVM 决定,不要依赖它!

2.4. Java 原生接口(JNI)

JNI 是 Java 代码与本地代码(如 C/C++)交互的桥梁。

  • ✅ 允许 Java 调用本地方法(如操作系统 API)
  • ✅ 也允许本地代码回调 Java 方法

在需要高性能计算或访问平台特有功能时非常有用。

2.5. 原生库(Native Libraries)

平台相关的库文件,包含本地方法的具体实现。例如,libjava.so(Linux)或 java.dll(Windows)。

3. JRE(Java 运行时环境)

JRE 是运行 Java 程序所需的软件集合。

它包含:

  • ✅ 一个 JVM 实现
  • ✅ 运行 Java 程序所需的核心类库
  • ✅ 配置属性文件

换句话说,JRE = JVM + 核心类库 + 配置。

3.1. 启动类(Bootstrap Classes)

位于 jre/lib/ 目录下,也称为启动类路径(bootstrap classpath)。包含:

  • rt.jar:核心运行时类(如 java.lang.*, java.util.*
  • i18n.jar:国际化支持
  • charsets.jar:字符集转换
  • 其他基础类库

启动类加载器加载。

3.2. 扩展类(Extension Classes)

位于 jre/lib/ext/ 目录(注意:Java 9+ 已废弃),也称为扩展类路径

  • jfxrt.jar:JavaFX 运行时
  • localedata.jar:区域数据支持

开发者可以将自己的 JAR 包放入此目录,自动被加载。

3.3. 属性配置(Property Settings)

JRE 使用这些配置文件维护平台设置,位于 jre/lib/ 及其子目录:

  • calendar.properties:日历配置
  • logging.properties:日志配置
  • net.properties:网络配置
  • deploy/:部署属性
  • management/:管理属性

3.4. 其他文件

  • jre/lib/security:安全管理
  • jre/lib/applet:Applet 支持类
  • jre/lib/fonts:字体文件

4. JDK(Java 开发工具包)

JDK 是用于开发、编译、调试和运行 Java 程序的完整工具包。

它包含:

  • ✅ JRE(所以能运行程序)
  • ✅ 一系列开发工具

4.1. 基础工具

开发的基石工具:

工具 用途
javac 编译 Java 源码为字节码
java 启动 Java 程序
javadoc 从源码生成 API 文档
jar 打包 JAR 文件
jdb 命令行调试器
javap 反编译 class 文件,查看字节码
extcheck 检查 JAR 版本冲突

4.2. 安全工具

管理密钥、证书和安全策略:

  • keytool:管理密钥库(keystore),生成密钥对和证书
  • jarsigner:对 JAR 文件进行数字签名
  • policytool:配置安全策略文件

Kerberos 相关工具(用于网络认证):

  • kinit:获取 Kerberos 票据
  • klist:查看本地票据缓存
  • ktab:管理密钥表

4.3. 国际化工具

  • native2ascii:将本地字符编码的文件转换为 ASCII 或 Unicode 转义序列,解决乱码问题。

4.4. RMI 工具

支持远程方法调用,开发分布式应用:

  • rmic:生成远程对象的存根(stub)和骨架(skeleton)
  • rmiregistry:启动 RMI 注册中心
  • rmid:启动激活系统守护进程
  • serialver:查看类的序列化版本 UID

4.5. Java IDL 和 RMI-IIOP 工具

支持 CORBA 分布式计算:

  • idlj:将 OMG IDL 文件编译为 Java 代码
  • orbd:提供对象请求代理服务
  • tnameserv:命名服务
  • servertool:管理持久化服务器

4.6. Java 部署工具

  • pack200:压缩 JAR 文件(使用更高效的压缩算法)
  • unpack200:解压 pack200 文件

4.7. Java 插件工具

  • htmlconverter:将包含 Applet 的 HTML 页面转换为 Java 插件兼容格式。

4.8. Java Web Start 工具

  • javaws:通过浏览器一键下载并启动 Java 应用程序,无需安装。

4.9. 监控与管理工具

性能调优必备!

  • jconsole:图形化监控工具(内存、线程、GC 等)
  • jps:列出本机所有 Java 进程(类似 ps
  • jstat:监控 JVM 统计信息(GC、类加载等)
  • jstatd:远程监控 JVM

4.10. 故障排查工具

线上问题定位利器(部分为实验性工具):

  • jmap:打印堆内存映射或详细信息(可生成 heap dump)
  • jstack:打印 Java 线程的堆栈跟踪(排查死锁、CPU 飙高)
  • jinfo:查看或修改 JVM 启动参数
  • jsadebugd:作为调试服务器附加到 Java 进程

5. 总结

三者关系一图流:

JDK
├── JRE
│   ├── JVM
│   │   ├── 类加载器
│   │   ├── 运行时数据区
│   │   ├── 执行引擎
│   │   └── ...
│   ├── 核心类库 (rt.jar 等)
│   └── 配置文件
├── javac, java, jar, javadoc ...
├── keytool, jarsigner ...
└── jconsole, jmap, jstack ...
  • 开发 Java?装 JDK
  • 运行 Java?装 JRE(现在通常直接装 JDK 也行)。
  • 执行字节码?靠 JVM

理解这三者的层次关系,能让你在搭建环境、分析问题时少走弯路。比如,线上服务 CPU 飙高,你可以用 jstack(JDK 工具)抓取线程栈;内存溢出,用 jmap 生成 dump 分析。这些都是 JDK 赋予开发者的“超能力”。


原始标题:Difference Between JVM, JRE, and JDK