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的堆区通常被分为年轻代老年代。年轻代又细分为EdenSurvivor区。

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

方法区

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

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

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

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

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