跳至主要內容

运行时数据区

会敲代码的程序猿原创JVMJVM大约 6 分钟

运行时数据区

运行时数据区是指在运行程序时存储数据的内存区域。分为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区五个部分。

Java虚拟机运行时数据区
Java虚拟机运行时数据区
  • 线程私有:
    • 程序计数器 - 存储线程执行位置
    • 虚拟机栈 - 存储Java方法调用与执行过程的数据
    • 本地方法栈 - 存储本地方法的执行数据
  • 线程共享:
    • - 主要存储对象
    • 方法区 - 存储类/方法/字段等定义(元)数据
    • 运行时常量区 - 保存常量static数据

程序计数器

程序计数器是线程私有的,用于记录当前程序执行的字节码指定位置。

知识点:

  1. 线程私有
  2. 不会被垃圾回收
  3. 访问速度最快(JVM内存区域中)
  4. 占用内存少,不会出现OutOfMemoryError
  5. 执行Java方法时,记录的是字节码指令地址
  6. 执行Native方法时,记录为未定义(undefined

思考以下问题,加强理解:

  1. 程序计数器如何保证线程能够准确地恢复到之前的执行位置?
  2. 字节码执行与程序计数器的关系?

虚拟机栈

虚拟机栈是线程私有的内存区域,其生命周期与线程相同。 它描述了方法执行的内存模型。当方法被执行时,JVM会为该方法同步创建一个栈帧(Stack Frame)

知识点:

  1. 线程私有
  2. 不会被垃圾回收
  3. 访问速度仅次于程序计数器
  4. 栈大小可设置,限制深度:
    • 推荐固定大小设置(-Xss 数值[k|m|g]),达到上限,抛出StackOverflowError
    • 动态扩展,可用内存不足时,抛出OutOfMemoryError

栈帧的内部结构:

栈帧的概念结构
栈帧的概念结构
  • 局部变量表: 用于存储方法中的局部变量和参数。
  • 操作数栈: 后进先出(LIFO)结构,用于方法执行时存储执行指令产生中间结果。
  • 动态链接: 指在方法调用时,将符号引用转换为直接引用的过程。
  • 方法返回地址: 指方法调用后返回位置的地址。

本地方法栈

本地方法栈是线程私有,与虚拟机栈功能相似。其中虚拟机栈为Java方法(字节码)服务,本地方法栈则为Native方法服务。

  • HotSpot虚拟机把虚拟机栈和本地方法栈合二为一。
  • 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowErrorOutOfMemoryError异常。

Java堆

Java堆是虚拟机管理的内存中最大的一块,线程共享,并在虚拟机启动时创建。 它的唯一目的是存放对象实例,几乎所有的对象实例以及数组都在堆上分配。

堆内存模型:

堆内存模型
堆内存模型

现代垃圾收集器采用分代收集理论进行设计,因此堆内存被划分为多个区域,包括:

  • 新生代:
    • 存放生命周期较短的对象
    • 通常由Eden区和两个Survivor区(被称为from/to或s0/s1)组成,默认比例是8:1:1
    • 填满时触发Minor GC(小型垃圾回收)
    • 采用复制算法,将存活的对象复制到Survivor区,然后清理Eden区和使用过的Survivor区。
  • 老年代:
    • 存放生命周期较长,或多次垃圾收集后任然存活的对象
    • 填满时触发Major GCFull GC,耗时严重
    • 使用的垃圾收集算法通常是标记-清除算法或标记-整理算法。
  • 永久代(PermGen):
    • 存放Class元数据,包括类结构、方法、字段信息等
    • 属于“堆”的一部分,无法扩展时会抛出OutOfMemoryError异常
    • 通过命令-Xms设置初始堆大小,-Xmx设定最大堆大小
    • 从JDK8开始,被元空间(Metaspace)取代,称为“非堆”,使用的是本地内存

DigitalOcean——Java (JVM) 内存模型 - Java 中的内存管理open in new window

方法区

方法区是JVM规范中的一个逻辑区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。

  • 在Java7的时候,方法区被称为“永久代(PermGen)”。
  • 从Java8开始,方法区的实现被改为元空间(Metaspace),元空间使用的是本地内存,而不是像永久代那样在JVM的堆内存中分配。

运行时常量池

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量与符号引用,支持在运行时动态添加新的常量。

  • 字面量: 表示固定的数据值,如整数、浮点数、字符串等常量。
  • 符号引用: 一组符号,用于描述所引用的目标,包括类和接口的全限定名、字段和方法的名称。

知识点:

  1. 具备动态性,如String.intern()方法将字符串对象添加到运行时常量池中。
  2. 会产生OutOfMemoryError异常

思考以下问题,加强理解:

  1. Class常量池与运行时常量池的关系?

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也未在《Java虚拟机规范》中明确定义。 然而,由于其频繁使用且可能导致OutOfMemoryError异常,值得在此进行讨论。

关键点:

  • NIO的引入: JDK 1.4引入了NIO(New Input/Output)类,通过通道(Channel)和缓冲区(Buffer)实现了一种新的I/O方式。它使用本地(Native)函数库直接分配堆外内存,并通过在Java堆中的DirectByteBuffer对象进行引用和操作。
  • 性能优势: 这种方法能够显著提高性能,因为它避免了在Java堆和本地堆之间的数据复制,从而加快了I/O操作。
  • 内存限制: 虽然直接内存的分配不受Java堆大小的限制,但仍受到本机总内存(包括物理内存、SWAP分区或分页文件)大小和处理器寻址空间的限制。
  • 配置问题: 在配置虚拟机参数(如-Xmx)时,管理员通常会根据实际物理内存来设置Java堆的大小,但可能忽略直接内存的占用。如果各个内存区域的总和超过了物理内存限制,可能在动态扩展时导致OutOfMemoryError异常。