
🌈 个人主页: Hygge_Code
🔥 热门专栏:从0开始学习Java | Linux学习 | 计算机网络
💫 个人格言: "既然选择了远方,便不顾风雨兼程"

文章目录
- 内存模型
-
- [模型构成总览 🥝](#模型构成总览 🥝)
-
- 程序计数器
- 栈
- [本地方法栈(Native Method Stack)](#本地方法栈(Native Method Stack))
- 堆(Heap)
- 方法区(元空间/MetaSpace)
- 运行时常量池
- [直接内存(Direct Memory)](#直接内存(Direct Memory))
- JVM内存模型里的堆和栈有什么区别?🤔
内存模型
模型构成总览 🥝
根据 JDK 8 规范,JVM运行时内存共分为 虚拟机栈、堆、元空间、程序计数器、本地方法栈 五个部分。还有一部分内存叫 直接内存 ,属于操作系统的本地内存,也是可以直接操作的。

由上面这张图片,我们可知JVM的内存模型主要分为以下几个部分:
程序计数器
- 核心定位:线程私有、生命周期与线程绑定的最小内存区域,是JVM中唯一不会抛出OutOfMemoryError的区域
- 核心作用:记录当前线程正在执行的字节码指令地址(行号)。当线程切换、CPU时间片轮转时,程序计数器会保存线程的执行位置,让线程恢复时能从断点继续执行
- 补充特性:
- 执行Java方法时,计数器记录的是正在执行的虚拟机字节码指令地址
- 执行Native(本地)方法时,计数器值为
undefined(无定义)🐦🔥 - 线程私有,因此多线程环境下不会出现线程安全问题
栈
- 核心定位:线程私有、生命周期与线程完全一致的内存区域,也叫Java栈
- 核心作用:存储Java方法执行的栈帧(Stack Frame),每个Java方法从调用到执行完成,对应一个栈帧在虚拟机栈中 ( 入栈→出栈 ) 的过程
- 栈帧核心结构:
- 局部变量表:存储方法的入参、局部变量、基本数据类型和对象引用(reference)
- 操作数栈:字节码执行的临时数据区,用于运算、方法调用传参
- 动态链接 :指向运行时常量池的方法引用,支持方法的动态绑定
- 方法返回地址:方法执行结束后,恢复调用者的执行位置
- 异常场景:
StackOverflowError:栈深度超过虚拟机限制(如无限递归)OutOfMemoryError:虚拟机栈无法动态扩展(如创建大量线程耗尽内存)
本地方法栈(Native Method Stack)
- 核心定位:线程私有,作用与虚拟机栈几乎完全一致,HotSpot虚拟机中直接将其与虚拟机栈合并实现
- 核心作用:为JVM调用的Native(本地)方法(如C/C++实现的底层方法)提供内存支持,存储Native方法执行的栈帧
- 补充说明:
- 服务于非Java语言编写的方法,是JVM与操作系统底层交互的桥梁
- 异常类型与虚拟机栈完全一致,同样会抛出
StackOverflowError和OutOfMemoryError
堆(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堆大小限制,但受操作系统总内存限制,可通过
JVM内存模型里的堆和栈有什么区别?🤔
-
用途:栈 主要用于存储局部变量、方法调用的参数、方法的返回地址以及一些临时数据。每当一个方法被调用,一个栈帧就会在栈中创建,用于存储该方法的信息,当方法执行完毕,栈帧也会被移除。堆用于存储对象的实例(包括类的实例和数组),当你使用
new关键字创建一个对象时,对象的实例就会在堆上分配空间。 -
声明周期:栈 上的数据具有确定的生命周期,当一个方法的调用结束时,其对应的栈帧就会被销毁,栈中存储的局部变量也会随之消失。堆中的对象生命周期不确定,对象会在垃圾回收机制(GC)检测到对象不再被引用时才会被回收。
-
存取速度:栈 的存取速度通常比堆快,因为栈的操作简单快速。而堆的存取速度比较慢,因为对象在堆上的分配和回收需要更多的时间,而且垃圾回收机制的运行也会影响性能。
-
存储空间:栈 的空间相对固定且较小。当栈溢出 时(
StackOverflowError),通常是因为递归过深或局部变量过大。堆 的空间较大,动态扩展。堆溢出 (OutOfMemoryError)通常是因为创建了太多的大对象或者未能及时回收不再使用的对象 -
可见性:栈中的数据对线程是私有的,每个线程都有自己的栈空间。堆中的数据对线程是共享的,所有线程都可以访问堆上的对象
如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!
