JVM 的运行时数据区分为哪几个主要部分?
根据《Java虚拟机规范》,JVM 的运行时数据区(Runtime Data Area)主要分为 5 个核心部分。
为了更好地理解,我们通常按照 “是否被所有线程共享” 将它们分为两大类:线程私有区域 和 线程共享区域。
一、 线程私有区域(生命周期与线程相同)
这部分内存区域伴随线程的创建而生,线程的销毁而灭,相互之间互不干扰。
1. 程序计数器(Program Counter Register)
- 作用:可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令(分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖它完成)。
- 特点:
- 如果线程正在执行一个 Java 方法,它记录的是正在执行的虚拟机字节码指令的地址。
- 如果正在执行的是 Native 方法,它的值为空(Undefined)。
- 异常:这是整个 JVM 内存规范中 唯一一个没有规定任何
OutOfMemoryError(OOM) 的区域。
2. Java 虚拟机栈(Java Virtual Machine Stack)
- 作用:描述 Java 方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储:
- 局部变量表:存放基本数据类型、对象引用和 returnAddress。
- 操作数栈:方法执行过程中的工作空间,用于计算和传递参数。
- 动态链接:指向运行时常量池中该栈帧所属方法的引用。
- 方法出口信息:方法正常或异常返回的地址。
- 异常:
StackOverflowError:线程请求的栈深度大于虚拟机允许的深度(常见于无限递归)。OutOfMemoryError:如果栈允许动态扩展,当无法申请到足够内存时抛出。
3. 本地方法栈(Native Method Stack)
- 作用:与 Java 虚拟机栈发挥的作用非常相似,区别在于虚拟机栈为 JVM 执行 Java 方法(字节码)服务,而本地方法栈则为 JVM 使用到的 Native 方法(通常是 C/C++ 编写的底层方法) 服务。
- 异常:同样会抛出
StackOverflowError和OutOfMemoryError。 - (注:在主流的 HotSpot 虚拟机中,已经将 Java 虚拟机栈和本地方法栈合二为一了。)
二、 线程共享区域(生命周期与 JVM 相同)
这部分内存区域随虚拟机的启动而创建,随虚拟机的关闭而销毁,被所有线程共享。
4. 堆(Heap)
- 作用:这是 JVM 内存中最大的一块。它唯一的目的就是存放对象实例和数组(“几乎”所有的对象实例都在这里分配内存)。
- 特点:
- 是垃圾收集器(GC) 管理的主要区域,因此也被称为“GC 堆”。
- 从垃圾回收的角度,堆通常还可以细分为:新生代(Eden区、Survivor区)和老年代。(注:具体划分取决于使用的垃圾收集器,如 G1、ZGC 的物理划分有所不同)。
- 异常:当堆中没有足够内存完成实例分配,且堆也无法再扩展时,抛出
OutOfMemoryError。
5. 方法区(Method Area)
- 作用:用于存储已被虚拟机加载的 类信息(元数据)、常量、静态变量、即时编译器(JIT)编译后的代码缓存 等数据。
- 运行时常量池(Runtime Constant Pool):是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(用于存放编译期生成的各种字面量和符号引用),这部分内容在类加载后存放到运行时常量池中。
- 实现演进(重点):
- JDK 7 及以前:HotSpot 虚拟机使用永久代(PermGen)来实现方法区。同时,将字符串常量池和静态变量移动到了堆中。
- JDK 8 及以后:彻底废弃了永久代,改用在本地内存(Native Memory)中实现的元空间(Metaspace)来代替。也就是说,现在的类元数据信息存在操作系统的直接内存中,不再受 JVM 堆内存的限制(但受限于本机总内存)。
- 异常:当方法区无法满足新的内存分配需求时,将抛出
OutOfMemoryError。
补充:直接内存(Direct Memory)
虽然直接内存并不属于 JVM 规范中定义的运行时数据区的一部分,但它在 Java 开发中被频繁使用,且可能导致内存溢出,所以面试和学习时通常会一并提及。
- 作用:在 JDK 1.4 中新加入了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的
DirectByteBuffer对象作为这块内存的引用进行操作。 - 优点:避免了在 Java 堆和 Native 堆中来回复制数据,提高了性能。
- 异常:当本机的物理内存耗尽时,会抛出
OutOfMemoryError。
总结一张图:
plaintext
|---------------------------------------------------------|
| JVM 运行时数据区 |
| |
| 【线程共享】 【线程私有】 |
| +------------------+ +-------------------+ |
| | 堆 (Heap) | | 程序计数器 (PC) | |
| | (对象实例、数组) | +-------------------+ |
| +------------------+ | Java 虚拟机栈 | |
| | 方法区 (Method) | | (局部变量、栈帧) | |
| | (类信息、常量池) | +-------------------+ |
| | * JDK8改为元空间 | | 本地方法栈 | |
| +------------------+ | (Native 方法) | |
|---------------------------------------------------------|
右滑查看面试常问