1. 概述
Java 虚拟机(JVM)是一台抽象计算机,负责执行 Java 程序。 它通过执行编译后的 Java 字节码指令来运行程序,为此需要内存来存储运行所需的数据和指令。这些内存被划分为不同的区域。
本文将深入探讨 JVM 的各类运行时数据区域及其作用。所有 JVM 实现都必须遵循官方规范中的设计要求。
2. 共享数据区
JVM 中存在多个线程共享的数据区,所有运行中的线程都能同时访问这些区域。
2.1. 堆内存
堆(Heap)是存放所有 Java 对象的运行时数据区。 每当创建类实例或数组时,JVM 都会在堆中寻找可用内存分配给对象。堆在 JVM 启动时创建,在 JVM 退出时销毁。
根据规范,对象存储必须由自动管理工具处理——也就是垃圾收集器。
⚠️ 规范未限制堆的大小,内存管理也由具体 JVM 实现。但如果垃圾收集器无法回收足够空间创建新对象,JVM 会抛出 OutOfMemoryError
。
2.2. 方法区
方法区存储类和接口的定义信息,是线程共享的数据区。 它在 JVM 启动时创建,直到 JVM 退出才销毁。
具体流程:类加载器加载字节码后传递给 JVM,JVM 创建类的内部表示(包含字段、方法、构造器等信息),用于运行时创建对象和调用方法。
✅ 方法区是逻辑概念,具体实现中可能作为堆的一部分。
⚠️ 规范同样未定义方法区大小和内存管理方式。当加载新类时空间不足,会抛出 OutOfMemoryError
。
2.3. 运行时常量池
运行时常量池是方法区的一部分,存储类/接口名、字段名、方法名等符号引用。
JVM 在创建类/接口的内部表示时,会同步创建其运行时常量池。如果创建时方法区内存不足,直接抛出 OutOfMemoryError
。
3. 线程私有数据区
除共享数据区外,JVM 还为每个线程维护私有数据区,因为 JVM 支持多线程并发执行。
3.1. 程序计数器
每个 JVM 线程都有独立的程序计数器(PC Register)。其行为取决于方法类型:
- 执行非 Native 方法时:存储当前指令的地址
- 执行 Native 方法时:值为 undefined
❌ 程序计数器的生命周期与线程绑定,线程结束即销毁。
3.2. JVM 栈
每个线程拥有私有的JVM 栈。每次方法调用都会在栈中创建新栈帧,存储局部变量和返回地址(栈帧可能存储在堆中)。
通过 JVM 栈,JVM 能跟踪程序执行流程,并在需要时生成堆栈跟踪。
⚠️ 栈大小和内存分配由具体 JVM 实现:
- 内存分配失败 →
StackOverflowError
- 动态扩展栈时内存不足 →
OutOfMemoryError
3.3. 本地方法栈
本地方法指非 Java 语言实现的方法(如 C/C++),它们不被编译成字节码,需要独立内存区。
本地方法栈与 JVM 栈结构类似,但专用于本地方法,用于跟踪本地方法的执行。
⚠️ 内存管理规则与 JVM 栈一致:
- 分配失败 →
StackOverflowError
- 扩展失败 →
OutOfMemoryError
✅ 规范允许 JVM 实现不支持本地方法调用,这种情况下无需实现本地方法栈。
4. 总结
本文系统解析了 JVM 运行时数据区的类型和作用。这些区域是 JVM 正常运行的核心,深入理解它们有助于优化 Java 应用性能。