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 赋予开发者的“超能力”。