JVM通关指南(二)JVM运行时数据区的深入解析
JVM运行时数据区
JVM运行时数据区是Java虚拟机在执行Java程序时使用的内存区域,它被划分为几个不同的部分,每个部分有特定的用途,下面是JVM运行时数据区主要组成部分。

方法区(Method Area)
- 线程共享的内存区域
- 存储已被JVM加载的:类信息、常量、静态变量、即时编译器编译后的代码
- 在HotSpot JVM中也被称为”永久代”(PermGen),但在Java 8中被”元空间”(Metaspace)取代
- 当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常
Java堆(Java Heap)
- 线程共享的内存区域
- 在JVM启动时创建
- 存储所有对象实例和数组
- GC管理的主要区域(“GC堆”)
- 内存回收角度来看java堆可分为:新生代和老生代。
- 堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
程序计数器(Program Counter Register)
- 线程私有的内存区域
- 记录当前线程所执行的字节码行号指示器
- 执行Java方法时记录正在执行的虚拟机字节码指令地址
- 执行Native方法时值为空(Undefined)
- 唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域
在Java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的电脑(CPU)。但在CPU上面去运行,有个非常不稳定的因素,叫做调度策略,这个调度策略是基于时间片的,也就是当前的这一纳秒是分配给那个指令的。
线程A在看直播:

现在线程B来了一个视频电话,就会抢夺线程A的时间片,则打断了线程A,线程A就会挂起:

当线程B的视频电话结束,这时线程A在做什么呢?线程是最小的执行单位,他不具备记忆功能,它只负责去干,那这个记忆就由程序计数器来记录:

Java虚拟机栈(Java Virtual Machine Stacks)
- 线程私有的内存区域
- 生命周期与线程相同
- 存储栈帧(Stack Frame),每个方法执行时都会创建一个栈帧
- 可能抛出StackOverflowError(栈深度超过限制)和OutOfMemoryError(无法扩展栈时)
- 栈帧包含:
- 局部变量表(Local Variable Table):存储方法参数和局部变量
- 操作数栈(Operand Stack):方法执行的工作区
- 动态链接(Dynamic Linking):指向运行时常量池的方法引用
- 方法返回地址(Return Address):方法执行完毕后的返回位置
常见的QA
一个方法调用另一个方法,会创建很多栈帧吗?
答:会创建,如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面。
栈指向堆是什么意思?
栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址。
递归的调用自己会创建很多栈帧吗?
递归会创建多个栈帧,就是一直排下去。
本地方法栈(Native Method Stack)
- 线程私有的内存区域
- 为JVM使用到的Native方法服务
- 与Java虚拟机栈类似,只是为Native方法服务
- 可能抛出StackOverflowError和OutOfMemoryError
运行时常量池(Runtime Constant Pool)
- 方法区的一部分
- 存储编译期生成的各种字面量和符号引用
- 动态性:可以在运行时将新的常量放入池中(String.intern())
- 可能抛出OutOfMemoryError
直接内存(Direct Memory)
- 不是JVM规范定义的内存区域
- 通过NIO的DirectByteBuffer分配
- 不受Java堆大小限制,但受本机总内存限制
- 可能抛出OutOfMemoryError
直接内存与堆内存的区别
- 接内存申请空间耗费很高的性能,堆内存申请空间耗费比较低
- 直接内存的IO读写的性能要优于堆内存,在多次读写操作的情况相差非常明显
代码示例
1 | import java.nio.ByteBuffer; |
运行结果
1 | 在进行10000000次分配操作时,堆内存:分配耗时:98ms |
这些内存区域协同工作,支持Java程序的执行,每个区域都有其特定的用途和生命周期管理方式。