🌈 个人主页: Hygge_Code
💫 个人格言: “既然选择了远方,便不顾风雨兼程”

在这里插入图片描述

内存模型

模型构成总览 🥝

根据 JDK 8 规范,JVM运行时内存共分为 虚拟机栈、堆、元空间、程序计数器、本地方法栈 五个部分。还有一部分内存叫 直接内存 ,属于操作系统的本地内存,也是可以直接操作的。

在这里插入图片描述

由上面这张图片,我们可知JVM的内存模型主要分为以下几个部分:

程序计数器

  • 核心定位线程私有、生命周期与线程绑定的最小内存区域,是JVM中唯一不会抛出OutOfMemoryError的区域
  • 核心作用:记录当前线程正在执行的字节码指令地址(行号)。当线程切换、CPU时间片轮转时,程序计数器会保存线程的执行位置,让线程恢复时能从断点继续执行
  • 补充特性
    • 执行Java方法时,计数器记录的是正在执行的虚拟机字节码指令地址
    • 执行Native(本地)方法时,计数器值为undefined(无定义)🐦‍🔥
    • 线程私有,因此多线程环境下不会出现线程安全问题

  • 核心定位线程私有、生命周期与线程完全一致的内存区域,也叫Java栈
  • 核心作用:存储Java方法执行的栈帧(Stack Frame),每个Java方法从调用到执行完成,对应一个栈帧在虚拟机栈中 ( 入栈→出栈 ) 的过程
  • 栈帧核心结构
    1. 局部变量表:存储方法的入参、局部变量、基本数据类型和对象引用(reference)
    2. 操作数栈:字节码执行的临时数据区,用于运算、方法调用传参
    3. 动态链接:指向运行时常量池的方法引用,支持方法的动态绑定
    4. 方法返回地址:方法执行结束后,恢复调用者的执行位置
  • 异常场景
    • StackOverflowError:栈深度超过虚拟机限制(如无限递归)
    • OutOfMemoryError:虚拟机栈无法动态扩展(如创建大量线程耗尽内存)

本地方法栈(Native Method Stack)

  • 核心定位线程私有,作用与虚拟机栈几乎完全一致,HotSpot虚拟机中直接将其与虚拟机栈合并实现
  • 核心作用:为JVM调用的Native(本地)方法(如C/C++实现的底层方法)提供内存支持,存储Native方法执行的栈帧
  • 补充说明
    • 服务于非Java语言编写的方法,是JVM与操作系统底层交互的桥梁
    • 异常类型与虚拟机栈完全一致,同样会抛出StackOverflowErrorOutOfMemoryError

堆(Heap)

  • 核心定位线程共享的最大内存区域,是垃圾回收(GC)的主战场,也是OOM最常发生的区域
  • 核心作用:存储所有Java对象实例、数组,是Java程序内存分配的核心区域
  • 补充特性
    • 图中堆区域包含 运行时常量池(JDK8后,运行时常量池从方法区迁移至堆中)
    • 堆被垃圾回收器划分为年轻代(Eden + S0/S1 Survivor)、老年代,用于分代回收
    • 可通过-Xms/-Xmx参数设置堆的初始/最大大小
    • 典型OOM:java.lang.OutOfMemoryError: Java heap space(内存泄漏、对象过多)

方法区(元空间/MetaSpace)

  • 核心定位线程共享的内存区域,JDK8后用 [ 元空间 ] 替代了永久代(PermGen),直接使用本地内存,不再受JVM堆大小限制
  • 核心作用:存储类的元数据信息,包括:类结构、字段、方法、构造器、即时编译(JIT)后的机器码等
  • 补充特性
    • 元空间默认无大小上限,可通过-XX:MaxMetaspaceSize限制最大内存
    • 支持类卸载:当类加载器不再被引用时,对应的类元数据会被GC回收
    • 典型OOM:java.lang.OutOfMemoryError: Metaspace(动态生成大量类、CGLib代理滥用)

运行时常量池

  • 核心定位堆内存的子区域,用于存放编译期生成的各种字面量和符号引用
  • 核心作用:存储编译期生成的字面量(如字符串、数字)、符号引用量(类/方法/字段的引用),并在运行期动态生成新的常量(如String.intern()
  • 补充说明
    • JDK7及之前,运行时常量池属于方法区(永久代);JDK8后,随永久代移除,被迁移至Java堆中
    • 是类加载过程中解析阶段的核心依赖,用于将符号引用转换为直接引用

直接内存(Direct Memory)

  • 核心定位不属于JVM运行时数据区的本地内存,由操作系统直接管理
  • 核心作用:通过Java NIO的ByteBuffer.allocateDirect()分配,用于堆外内存操作,避免Java堆与Native堆之间的数据拷贝,大幅提升I/O性能(如大文件读写、网络通信)
  • 补充特性
    • 不受JVM堆大小限制,但受操作系统总内存限制,可通过-XX:MaxDirectMemorySize设置上限
    • 内存回收依赖Cleaner机制(ByteBuffer对象被GC时自动回收),或手动调用clean()方法
    • 典型OOM:java.lang.OutOfMemoryError: Direct buffer memory(堆外内存分配超限)

JVM内存模型里的堆和栈有什么区别?🤔

  • 用途 主要用于存储局部变量、方法调用的参数、方法的返回地址以及一些临时数据。每当一个方法被调用,一个 栈帧 就会在栈中创建,用于存储该方法的信息,当方法执行完毕,栈帧也会被移除。用于存储对象的实例(包括类的实例和数组),当你使用 new关键字创建一个对象时,对象的实例就会在堆上分配空间。

  • 声明周期上的数据具有确定的生命周期,当一个方法的调用结束时,其对应的栈帧就会被销毁,栈中存储的局部变量也会随之消失。中的对象生命周期不确定,对象会在垃圾回收机制(GC)检测到对象不再被引用时才会被回收。

  • 存取速度的存取速度通常比堆快,因为栈的操作简单快速。而的存取速度比较慢,因为对象在堆上的分配和回收需要更多的时间,而且垃圾回收机制的运行也会影响性能。

  • 存储空间的空间相对固定且较小。当栈溢出时(StackOverflowError),通常是因为递归过深或局部变量过大。的空间较大,动态扩展。堆溢出OutOfMemoryError)通常是因为创建了太多的大对象或者未能及时回收不再使用的对象

  • 可见性中的数据对线程是私有的,每个线程都有自己的栈空间。中的数据对线程是共享的,所有线程都可以访问堆上的对象

OutOfMemoryErrorStackOverFlowError的区别 ⚠️

由于太过常见且重要,再单独拎出来总结一下🤔:

  • StackOverFolwError:如果栈的内存大小不允许动态扩展,那么当线程请求栈的深度超出当前Java虚拟机栈的最大深度的时候,就会抛出StackOverFolwError异常。
  • OutOfMemoryError:如果栈的内存大小可以动态扩展,那么当虚拟机在动态扩展栈时无法申请到足够的内存空间,就会抛出OutOfMemoryError异常。

如果我的内容对你有帮助,请 点赞 评论 收藏 。创作不易,大家的支持就是我坚持下去的动力!
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐