一个 jvm 中的内存分布大致如下:

程序计数器

程序计数器的值表示当前执行到的指令的位置,这点与其在其他地方的定义相同,需要注意的是在执行本地方法时,这个值应该为 undefined。

虚拟机栈

每一个方法的调用/返回对应着虚拟机栈的栈帧的压栈和出栈。

一个栈帧中存放以下内容:

  • 局部变量表
  • 操作数栈
  • 动态连接
  • 方法出口

局部变量表

局部变量表中保存着编译器可知的基本数据类型变量,对象引用和 returnAddress 类型。这些变量在局部变量表中以局部变量槽来表示,局部变量槽的大小为 32 位,除了 64 位的 long 和 double 需要占用两个槽表示,其余类型只占用一个槽。

在方法的运行期间不会改变局部变量表的大小。

简单的来说局部变量表中保存着所以方法的本地变量,需要注意的是局部变量表的大小并不是所有方法本地变量的和,应为涉及到槽的复用。

本地方法栈

和虚拟机栈类似,都是一个方法执行的上下文环境。区别在于虚拟机栈提供给 Jvm 中运行的程序使用,本地方法栈则是由 jvm 使用的原生方法使用(具体是哪种类型的语言的方法栈依赖于 jvm 是使用哪种语言实现的)。

堆中存放的是 java 对象,这个区域也是 jvm 垃圾回收器所管理的区域。

这个区域的大小是可扩展的,使用 jvm 参数 -Xms 指定最小堆内存大小,-Xmx 指定最大堆内存大小。

方法区

存放虚拟机加载的类的类型信息,常量,静态变量,即时编译器编译后的代码缓存。

需要注意的是,在 jdk7 的 hotspot 中,已经将字符串常量和静态变量等从永久代移除(hotspot 之前将垃圾收集器管理的区域扩展到了方法区,方法区即是垃圾收集区域中的永久代)。

String 作为 Java 中的特殊的类,其对象存储的区域可见:String在内存中如何存储(Java) - zeroingToOne - 博客园 (cnblogs.com)

运行时常量池

方法区的一部分。

class 文件中有一项信息是常量池表(Constant Pool Table),用于存放编译期生 成的各种字面量与符号引用。这部分内容将在类加载后存放到方法区的运行时常量池中。

除此之外,由常量池中的常量描述符解析(类加载的一个过程)得到的直接引用也存放在运行时常量池中。

直接内存

在 java 的 NIO 提供了一种基于通道(Channel)和缓冲区的 I/O 方式,可以使用 Native 函数库直接分配堆外内存,然后通过一个 java 堆中的 DirectByteBuffer 对象作为堆外内存的引用进行操作。

当然,在操作系统的层面,直接内存仍然属于 jvm 这个进程。