架构


区域共享性
程序计数器线程私有
Java虚拟机栈线程私有
本地方法栈线程私有
Java堆线程共享
方法区线程共享

程序计数器


程序计数器是一块内存较小的空间,记录当前线程当前正在执行的字节码指令地址。执行本地方法时,程序计数器的值为空(Undefined)。
程序计数器是唯一没有OutOfMemoryError (OOM)的区域,因为大小是固定的。

Java虚拟机栈


Java 虚拟机栈描述的是Java方法执行的内存模型。
栈的单位是栈帧:每个方法执行时都会创建一个栈帧,从方法调用到执行完毕对应着栈帧入栈到出栈的过程。
栈帧的结构

  1. 局部变量表:存放方法参数和方法内定义的局部变量(包括基本数据类型、对象引用和returnAddress等)。最小单位是变量槽。
  2. 操作数栈:后进先出栈,方法的执行计算过程中的临时数据存储区。
  3. 动态链接:栈帧保存了当前方法所属类的运行时常量池引用,用于支持动态链接。
  4. 方法返回地址:方法执行完毕退出后:
    1. 正常返回:有返回值则把返回值压入调用者的操作数栈,将PC恢复为调用指令的下一条指令地址,继续执行下一条指令。
    2. 异常返回:查找匹配的异常处理器,没有找到就向上传播,最终仍未处理则导致线程终止。
      相关参数配置
  • -Xss:设置每个线程的栈内存大小。例:-Xss1m
    相关异常
  • StackOverflowError:线程请求的栈深度大于虚拟机允许的深度(如无限递归)。
  • OutOfMemoryError
    • 虚拟机栈允许动态扩容:虚拟机栈尝试扩容时无法申请到足够的内存。
    • 虚拟机栈不允许动态扩容(如Hotspot):虚拟机无法申请到足够的内存创建新的线程。

本地方法栈


与Java虚拟机栈类似,但本地方法栈描述的是本地方法执行的内存模型。
相关异常

  • StackOverflowError:线程请求的栈深度大于虚拟机允许的深度(如无限递归)。
  • OutOfMemoryError
    • 虚拟机栈允许动态扩容(Hotspot不允许):虚拟机栈尝试扩容时无法申请到足够的内存。
    • 虚拟机无法申请到足够的内存创建新的线程。

Java堆


JVM中最大的一块内存区域,在JVM启动时创建,用于存放对象实例。几乎所有的对象实例以及数组都在这里分配内存。
根据生命周期的不同,可以将堆划分为新生代老生代,默认比例为1:2。

新生代

新生代内部又可以划分三个区域:

  • Eden:对象最初分配的地方。
  • Survivor 0 (S0、From):垃圾回收后存活的对象的区域。
  • Survivor 1 (S1、To):同S0。
    默认比例为8:1:1,原因:每次只使用一个Eden和一个Survivor区(90%),另一个Survivor区用作复制区域,内存浪费率只有10%(如果存活率大于10%,老年代进行空间分配担保,放不下的存活对象会放到老年代中)。
    相关参数配置
  • -Xms:初始堆大小。例:-Xms1g
  • -Xmx:最大堆大小。例:-Xmx1g
    相关异常
  • OutOfMemoryError: Java heap space:堆内存耗尽且Full GC后内存仍不足以创建新的对象。

方法区


方法区是JVM的规范,用于存储已经被虚拟机加载的类型信息、常量、类静态变量等。永久代和元空间是方法区的两种不同实现

永久代(PermGen)

JDK7以及之前,HotSpot方法区的实现为永久代,是堆外的一块内存,受限于-XX:MaxPermSize
相关参数配置

  • -XX:PermSize:永久代初始大小。
  • -XX:MaxPermSize:最大永久代大小。

元空间(Metaspace)

JDK8以及之后,彻底移除了永久代,改用元空间实现。
元空间不在虚拟机内存中,而是直接使用本地内存,内存受限于系统物理内存和MaxMetaspaceSize
相关参数配置

  • -XX:MaxMetaspaceSize:最大元空间大小。

运行时常量池

方法区的一部分。JVM加载一个类后会将Class文件中的常量池加载到运行时常量池中。

字符串常量池

  • JDK 6及之前:在永久代的字符串常量池中。
  • JDK 7移到了 Java 堆中
  • JDK 8及之后:仍然在 Java 堆中,元空间只保留类的元数据信息。

类静态变量

  • JDK 6及之前:在永久代中。
  • JDK 7移到了 Java 堆中
  • JDK 8及之后:仍然在 Java 堆中,元空间只保留类的元数据信息。

本地内存


本地内存是直接向操作系统申请的一块连续内存区域,不受JVM堆大小限制。

直接内存

直接内存指由Java NIO(如 ByteBuffer.allocateDirect()等)申请的直接缓冲区。
DirectByteBuffer引用堆外的直接缓冲区,避免了数据在JVM堆和内核缓冲区之间的二次拷贝(零拷贝),极大提升了网络和磁盘的I/O效率。
NIO相关结构:

  • :方法栈帧中的局部变量,持有DirectByteBuffer的引用。
  • DirectByteBuffer的实例对象(包含内存地址address、容量 capacity和Cleaner虚引用对象)。
  • 本地内存:通过操作系统malloc直接分配的连续内存块。
    回收机制:直接内存不由JVM GC直接管理。当DirectByteBuffer被GC回收时,内部的Cleaner虚引用对象会触发Unsafe.freeMemory(),释放相关的直接内存。
    相关参数配置
  • -XX:MaxDirectMemorySize:最大直接内存大小。
    相关异常
  • OutOfMemoryError: Direct buffer memory:直接内存耗尽,不足以创建新的缓冲区。

JIT 代码缓存(Code Cache)

JIT将热点方法的字节码编译为机器码,存放在本地内存中专用的Code Cache区域。