Java内存结构笔记

JVM内存结构

Java内存结构

Java内存结构主要分为5个部分:

  • 线程私有的:程序计数器、虚拟机栈、本地方法栈
  • 线程共享的:堆、方法区

线程私有

程序计数器

程序计数器(Program Counter, PC用于存储当前线程正在执行的字节码指令地址。

字节码执行过程中,程序计数器的值会随着程序的执行而不断更新,以保证程序能顺序执行、控制分支跳转或循环。

同时,线程切换后能恢复到正确的执行位置,也依赖于程序计数器

如果线程正在执行的是本地方法(Native Method),程序计数器的值为undefined,因为本地方法不通过字节码执行。

虚拟机栈

虚拟机栈主要用于存储方法调用时的栈帧,栈帧主要由局部变量表、操作数栈、动态链接、方法返回地址组成。每次调用方法时,JVM会为该方法分配一个栈帧,方法执行完成后栈帧出栈。

当栈的深度超过虚拟机栈的最大深度(通常是由于递归或死循环导致的),会抛出StackOverflowError。

栈帧的主要组成

  • 局部变量表:包括方法的参数、局部变量(基本类型、对象引用)
  • 操作数栈:存放方法执行过程中产生的中间计算结果和临时变量。
  • 动态链接:方法调用时,将符号引用解析为实际内存地址的过程。(是 JVM 处理多态、继承、接口等语言特性的基础)
  • 方法返回地址:调用一个方法时,记录调用方在方法调用之后的下一条指令的地址

动态链接和静态链接

特性 静态链接 动态链接
链接时机 类加载阶段(如 static 方法和 private 方法) 程序运行时
确定性 引用在加载时已确定,运行时无需解析 引用在运行时解析,支持动态绑定
性能 运行时性能较高,但缺乏灵活性 运行时有一定开销,但支持多态和动态特性
适应性 代码更新需要重新加载类 代码更新可以在运行时生效,提高系统适应性

本地方法栈

本地方法栈 和虚拟机栈类似,但它是专门为本地方法(Native Methods) 设计的。

Java 可以通过 JNI(Java Native Interface)调用 C/C++、汇编等语言编写的本地方法,JVM 会将这些方法调用放在本地方法栈中执行。

在 HotSpot JVM 中,直接将本地方法栈和虚拟机栈合二为一。线程的虚拟机栈其实是用来管理 Java 方法调用和本地方法调用的栈空间。当一个线程在执行 Java 方法时,该方法的栈帧会压入虚拟机栈;当该 Java 方法中调用了本地方法时,本地方法的栈帧也会压入同一个虚拟机栈中。

线程共享

堆

JVM中最大的内存区域,是JVM的主要垃圾回收区域。用于存储对象实例和数组,所有的对象都在堆中分配内存。

JVM的堆区通常被分为年轻代和老年代。年轻代又细分为Eden、Survivor区。

在JDK7还有永久代,JDK8移除了永久代,改为使用直接内存的Matespace。永久代(PermGen)和元空间(Metaspace)都是JVM规范中方法区(Method Area)的实现。

方法区

主要存储 类的元数据、常量、静态变量和方法的字节码。在JDK 8之后,方法区的实现 元空间使用本地内存,不再属于堆。

-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)

-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

常量池、运行时常量池、字符串常量池的关系

  • 常量池是编译期由Class文件中Constant Pool Table生成的一部分,存储了类或接口中的常量。
  • 运行时常量池是常量池的运行时表示,它在类加载时从.class文件中的常量池中加载,属于方法区的一部分。
  • 字符串常量池在堆上,运行时常量池保存在元空间中