Java内存模型核心是变量生命周期与内存区域的动态映射:局部基本类型存栈帧局部变量表,字符串字面量入常量池(JDK8+在堆),new对象分配在堆,static变量驻方法区(JDK8+为Metaspace),各区域溢出异常对应不同内存压力点。

Java 内存模型深度复习不是堆砌术语,而是用代码触发真实内存行为,再反向定位到各区域——从一句 int a = 10 就能串起栈、堆、方法区、常量池的协作逻辑。
以变量生命周期为线索,画出每行代码的内存落点
不靠死记,靠推演。看到变量声明,立刻问:它在哪分配?谁管理?何时释放?
-
局部基本类型(如
int age = 25) → 直接存入当前线程的虚拟机栈帧中“局部变量表”,值在栈里,无GC,方法结束自动弹出 -
字符串字面量(如
String s = "abc") → 字面量"abc"存入字符串常量池(JDK 7+ 在堆中;JDK 8+ 仍属堆内独立区域),引用s存在栈中,指向常量池地址 -
new 对象(如
String t = new String("abc")) → 常量池已有"abc",但new强制在堆中新建一个对象;栈中t指向堆对象,与常量池无关 -
static 变量(如
static int COUNT = 1) → 存在方法区(JDK 8+ 是 Metaspace),随类加载而存在,所有实例共享,生命周期与 JVM 同步
用装箱/拆箱和 == 比较暴露底层机制
自动装箱不是语法糖,是显式方法调用;== 不是比较“相等”,是在比内存地址——这是检验你是否真懂栈、堆、常量池关系的试金石。
-
Integer i = 100; Integer j = 100;→ 因在-128~127缓存范围内,i == j为true(共用同一个缓存对象) -
Integer m = 200; Integer n = 200;→ 超出缓存范围,各自new,m == n为false(堆中两个不同对象) -
Short s1 = 1; s1 += 1;正确,因复合赋值隐含强制转型;但s1 = s1 + 1编译失败,因s1 + 1结果是int,需手动强转
结合 GC 和 OOM 场景反推内存区域压力点
内存模型不是静态图纸,是动态战场。哪块区域频繁溢出,就说明哪块理解有断层。
立即学习“Java免费学习笔记(深入)”;
-
OutOfMemoryError: Java heap space→ 堆满,检查对象是否泄漏、大集合未清理、缓存未设上限 -
OutOfMemoryError: Metaspace→ 方法区(元空间)爆了,常见于动态生成类(如大量 CGLIB 代理、热部署未卸载类) -
StackOverflowError→ 栈帧过深,多见于无限递归或超深调用链,和堆大小无关,由-Xss控制单线程栈容量 -
OutOfMemoryError: unable to create new native thread→ 线程数超限,本质是本地方法栈或系统资源耗尽,非 JVM 堆问题
用 JMM 规则解释多线程下的“看不见”问题
JMM 不是内存结构图,是线程间通信契约。重点掌握 happens-before 如何让写操作对其他线程“可见”。
-
volatile字段写 → 后续同字段读一定能看到最新值(禁止重排序 + 强制刷主存) -
synchronized块退出 → 所有修改对下一个获取同一锁的线程可见(解锁前写回主存) - 构造器内
this逸出 → 违反安全发布原则,可能导致其他线程看到半初始化对象(典型双重检查单例失效原因) - 没有同步手段时,
boolean flag = true的写操作可能永远不被另一线程观察到——这不是 bug,是 JMM 允许的合法优化


















