运行时数据区与JMM内存模型(彻底区分、面试必背)

发布时间:2026/7/10 22:44:36
运行时数据区与JMM内存模型(彻底区分、面试必背) 我们是由枫哥组建的IT技术团队成立于2017年致力于帮助IT从业者提供实力成功入职理想企业我们提供一对一学习辅导由知名大厂导师指导分享Java技术、参与项目实战等服务并为学员定制职业规划全面提升竞争力过去8年我们已成功帮助数千名求职者拿到满意的OfferIT枫斗者、IT枫斗者-Java面试突击。一、为什么必须吃透 JVM 内存很多Java开发者工作多年遇到线上问题依旧无从下手常见的线上疑难故障几乎全部指向JVM内存OutOfMemoryError内存溢出StackOverflowError栈溢出项目频繁 Full GC接口卡顿隐形内存泄漏服务越跑越慢多线程并发线程安全问题CPU 100% 居高不下实战案例直观理解内存溢出如下代码无限创建对象最终会触发堆内存溢出ListUserlistnewArrayList();while(true){list.add(newUser());}运行报错java.lang.OutOfMemoryError:Javaheap space报错根源不断创建的对象全部占用堆内存最终堆内存被占满触发OOM。由此可见学好JVM第一步必须吃透内存结构。二、JVM 运行时数据区核心内存划分JVM启动后会自动开辟运行时数据区Runtime Data Area这是Java程序运行的核心内存空间整体分为两大类别线程共享内存、线程私有内存。内存整体结构图JVM Runtime Data Area 线程共享全局唯一 ┌─────────────────────┐ │ Heap 堆 │ ├─────────────────────┤ │ Method Area 方法区 │ └─────────────────────┘ 线程私有每个线程独立 ┌─────────────────────┐ │ Program Counter 程序计数器 │ ├─────────────────────┤ │ Java Stack 虚拟机栈 │ ├─────────────────────┤ │ Native Method Stack 本地方法栈 │ └─────────────────────┘1. 线程共享内存整个JVM中只有一份所有线程共享访问也是GC的核心工作区域Heap堆存储所有对象实例、数组Method Area方法区存储类元数据、常量、方法信息2. 线程私有内存每个线程独立创建、线程间互不干扰线程销毁时内存自动释放程序计数器Java虚拟机栈本地方法栈三、线程私有内存区域详解3.1 程序计数器Program Counter RegisterJVM中最小、唯一无OOM异常的内存区域。核心作用记录当前线程正在执行的字节码指令地址相当于线程的“执行进度条”。执行原理演示现有测试方法publicvoidtest(){inta1;intb2;intcab;}对应字节码指令0:iconst_11:istore_12:iconst_23:istore_24:iload_15:iload_26:iadd7:istore_3程序计数器会实时记录当前执行的指令行数例如当前执行到第5条指令。为什么需要线程私有CPU是时间片轮转调度会频繁切换线程执行A→B→C→A。线程被切换挂起时需要通过程序计数器记录执行位置线程恢复执行后可精准接续运行不会错乱。3.2 Java 虚拟机栈面试高频线程生命周期核心内存方法执行的专属内存空间。核心特性线程启动自动创建一个虚拟机栈方法调用创建一个栈帧Stack Frame方法执行结束栈帧自动出栈、释放内存线程销毁整个虚拟机栈回收执行流程演示publicstaticvoidmain(String[]args){test();}publicstaticvoidtest(){intage18;}栈结构后进先出┌──────────┐ │ test() │ 当前执行栈帧 ├──────────┤ │ main() │ 底层栈帧 └──────────┘3.3 栈帧核心结构每个方法对应一个栈帧栈帧是方法执行的最小单元包含四大核心模块┌────────────────────┐ │ Local Variables 局部变量表 │ ├────────────────────┤ │ Operand Stack 操作数栈 │ ├────────────────────┤ │ Dynamic Linking 动态链接 │ ├────────────────────┤ │ Return Address 方法返回地址 │ └────────────────────┘1局部变量表存储方法内的局部变量、基本数据类型、对象引用地址publicvoidtest(){intage18;StringnameTom;}存放内容age基本变量、name对象引用地址。2操作数栈JVM执行运算的临时栈空间所有加减乘除运算都基于操作数栈完成。执行int c a b流程1. push a压入a的值 2. push b压入b的值 3. add栈顶两个数相加 4. pop弹出结果赋值给c3.4 StackOverflowError 底层原理经典面试题栈溢出异常成因递归无终止代码演示publicvoidtest(){test();// 无限递归调用自身}执行过程不断创建新栈帧虚拟机栈空间持续膨胀超出JVM预设栈大小┌──────┐ │test │ ├──────┤ │test │ ├──────┤ │test │ ├──────┤ │test │ └──────┘最终栈空间耗尽抛出异常java.lang.StackOverflowError核心结论递归过深、方法嵌套调用过多导致栈帧数量超限触发栈溢出。3.5 本地方法栈Native Method Stack专门服务于native本地方法由C/C实现不属于Java代码范畴。示例线程启动底层方法publicnativevoidstart0();执行链路Thread.start() → start0() → 调用C/系统API本地方法栈即为这类原生方法提供内存支撑。四、线程共享内存区域详解4.1 堆Heap—— JVM最大内存区域整个JVM中内存最大、GC最频繁的区域所有线程共享。存储内容所有通过new创建的对象、数组实例newUser()newArrayList()newHashMap()newint[10]内存存储模型Stack虚拟机栈 └─ user对象引用地址 Heap堆 └─ 真实的User对象实例4.2 对象创建完整流程高频面试代码User user new User();完整5步执行流程类加载检查判断User.class是否被类加载器加载未加载则优先完成加载堆内存分配在堆中开辟对应大小的内存空间用于存储对象默认初始化对象字段赋予默认值引用null、数字0、布尔false构造方法初始化执行用户定义的构造函数覆盖默认值返回引用地址将堆中对象的内存地址赋值给栈中的引用变量4.3 方法区Method Area存储类的静态元数据JDK8是重要分水岭JDK8 之前永久代Permanent GenerationJDK8 及之后元空间Metaspace使用本地内存不受JVM堆限制核心存储内容类名、字段属性、方法信息字节码数据、常量池、静态变量接口、注解等元数据4.4 运行时常量池属于方法区的子模块核心作用是存储字符串常量、数字常量实现常量复用。示例字符串常量共享机制Stringaabc;Stringbabc;内存原理两个引用变量指向同一个常量池中的abc对象避免重复创建节省内存。五、JMMJava内存模型深度解析重中之重90%开发者会混淆 JVM内存结构 和 JMM5.1 核心区别JVM运行时数据区解决内存如何划分、存什么的物理内存问题JMM内存模型解决多线程之间如何安全读写共享内存的并发规则问题5.2 为什么需要JMMCPU为了提升执行效率会开启缓存、指令重排导致线程可见性、有序性问题。经典案例变量不可见问题privatebooleanflagfalse;// 线程A修改变量flagtrue;// 线程B循环读取while(!flag){// 死循环}理论线程A修改后线程B应立刻退出循环实际线程B可能永远死循环根源线程A的修改只存在自身CPU工作缓存中未刷新到主内存线程B无法感知最新值。5.3 JMM核心内存模型JMM规范规定所有线程变量必须存储在主内存线程无法直接操作主内存必须先拷贝到自身工作内存修改后再刷新回主内存。Main Memory主内存线程共享 │ ┌──────────┴──────────┐ │ │ Thread A Thread B Working Memory Working Memory 线程私有工作内存 线程私有工作内存标准读写流程主内存 → 工作内存 → 修改数据 → 刷新主内存5.4 volatile 解决可见性原理被volatile修饰的变量JMM会强制保证线程修改变量后立即刷新到主内存其他线程读取变量时强制从主内存读取最新值不读取本地缓存修复上述死循环问题privatevolatilebooleanflagfalse;5.5 JMM三大核心特性并发面试核心1. 原子性Atomicity操作不可分割要么全部执行成功要么全部失败。注意count非原子操作分为三步读取值 → 自增 → 写回主内存多线程下会出现线程安全问题。2. 可见性Visibility一个线程修改共享变量后其他线程能立刻感知到最新结果由volatile、synchronized保证。3. 有序性OrderingJVM和CPU为了优化效率会进行指令重排JMM会在规则内禁止非法重排保证多线程执行有序。六、高频面试真题汇总1. JVM运行时数据区包含哪些线程私有程序计数器、虚拟机栈、本地方法栈线程共享堆、方法区2. 堆和栈的核心区别栈线程私有存储局部变量、方法引用随线程销毁释放无GC堆线程共享存储对象实例由GC统一回收是内存溢出高发区3. StackOverflowError 成因方法递归过深、嵌套调用过多导致虚拟机栈帧数量超限栈空间耗尽。4. OOM 内存溢出常见原因堆内存不足、元空间耗尽、直接内存溢出、线程栈过多等。5. JMM和JVM内存结构的区别JVM内存结构物理内存分区定义内存存储规则JMM内存模型抽象并发规范定义多线程内存读写规则解决并发安全问题七、本章核心总结掌握以下两张核心图彻底吃透JVM内存底层1. JVM运行时数据区总览JVM Runtime Data Area 线程共享 ┌─────────────────┐ │ Heap 堆 │ ├─────────────────┤ │ Method Area 方法区 │ └─────────────────┘ 线程私有 ┌─────────────────┐ │ Program Counter 程序计数器 │ ├─────────────────┤ │ VM Stack 虚拟机栈 │ ├─────────────────┤ │ Native Stack 本地方法栈 │ └─────────────────┘2. JMM内存模型总览Main Memory 主内存 │ ┌─────────┴─────────┐ │ │ Working Memory Working Memory 线程A工作内存 线程B工作内存写在最后JVM内存是Java进阶、并发编程、性能调优的基石。分清物理内存分区JVM运行时数据区和并发内存规范JMM就能解决80%的Java内存与线程安全问题为后续GC调优、高并发实战打下坚实基础。⭐️推荐:Offer训练营介绍Java 面试 后端通用面试八股文Java后端企业级实战面试Java后端校招算法学习