jvm 运行时内存布局

 java 8 虚拟机规范的原始表达:(jvm)Run-Time Data Areas, 暂时翻译为“jvm 运行时内存布局”。

从概念上大致分为 6 个(逻辑)区域,参考下图。注:Method Area 中还有一个常量池区,图中未明确标出。

jvm 运行时内存布局

这 6 块区域按是否被线程共享,可以分为两大类:

jvm 运行时内存布局

一类是每个线程所独享的:

1)PC Register:也称为程序计数器, 记录每个线程当前执行的指令信。eg:当前执行到哪一条指令,下一条该取哪条指令。

2)JVM Stack:也称为虚拟机栈,记录每个栈帧(Frame)中的局部变量、方法返回地址等。注:这里出现了一个新名词“栈帧”,它的结构如下:

jvm 运行时内存布局

线程中每次有方法调用时,会创建 Frame,方法调用结束时 Frame 销毁。

3)Native Method Stack:本地 (原生) 方法栈,顾名思义就是调用操作系统原生本地方法时,所需要的内存区域。

上述 3 类区域,生命周期与 Thread 相同,即:线程创建时,相应的区域分配内存,线程销毁时,释放相应内存。

另一类是所有线程共享的:

1)Heap:即鼎鼎大名的堆内存区,也是 GC 垃圾回收的主站场,用于存放类的实例对象及 Arrays 实例等。

2)Method Area:方法区,主要存放类结构、类成员定义,static 静态成员等。

3)Runtime Constant Pool:运行时常量池,比如:字符串,int -128~127 范围的值等,它是 Method Area 中的一部分。

Heap、Method Area 都是在虚拟机启动时创建,虚拟机退出时释放。

注:Method Area 区,虚拟机规范只是说必须要有,但是具体怎么实现(比如: 是否需要垃圾回收? ),交给具体的 JVM 实现去决定,逻辑上讲,视为 Heap 区的一部分。所以,如果你看见类似下面的图,也不要觉得画错了。

jvm 运行时内存布局

上述 6 个区域,除了 PC Register 区不会抛出 *Error 或 OutOfMemoryError ,其它 5 个区域,当请求分配的内存不足时,均会抛出 OutOfMemoryError(即:OOM),其中 thread 独立的 JVM Stack 区及 Native Method Stack 区还会抛出 *Error。

最后,还有一类不受 JVM 虚拟机管控的内存区,这里也提一下,即:堆外内存。

jvm 运行时内存布局

可以通过 Unsafe 和 NIO 包下的 DirectByteBuffer 来操作堆外内存。如上图,虽然堆外内存不受 JVM 管控,但是堆内存中会持有对它的引用,以便进行 GC。

提一个问题:总体来看,JVM 把内存划分为“栈 (stack)”与“堆 (heap)”两大类,为何要这样设计?

个人理解,程序运行时,内存中的信息大致分为两类,一是跟程序执行逻辑相关的指令数据,这类数据通常不大,而且生命周期短;一是跟对象实例相关的数据,这类数据可能会很大,而且可以被多个线程长时间内反复共用,比如字符串常量、缓存对象这类。

将这两类特点不同的数据分开管理,体现了软件设计上“模块隔离”的思想。好比我们通常会把后端 service 与前端 website 解耦类似,也更便于内存管理。