jvm内存与对象生命周期主线:代码执行时,局部变量存虚拟机栈,new对象实例分配在堆(eden或直接老年代),类信息和static变量存方法区(metaspace),对象通过gc roots可达性判定生死,经minor gc、晋升、full gc完成回收,全过程由参数与日志可观测可调优。

Java JVM 深度复习不是罗列概念,而是用一条主线把内存、对象、执行、回收串起来——从代码写下去的那一刻开始,每个变量、每行 new、每次方法调用,都在对应内存区域里留下痕迹。重点是能指着一段代码,说清它在 JVM 里“落脚在哪、怎么活、何时死”。
以代码为起点,反推内存分配路径
看到一句 int age = 25;,立刻定位到虚拟机栈的局部变量表,4 字节值直接存;String s = new String("abc"); 则要拆解三处:栈中存引用 s,堆中新建 String 对象,常量池里已有 "abc" 字面量(若未存在则放入);static final List<string> cache = new ArrayList();</string> 中的 cache 引用和静态字段本身在方法区(JDK 8+ 是 Metaspace),而 new 出的 ArrayList 实例在堆。
- 局部变量、形参、方法内 new 的对象引用 → 虚拟机栈
- new 出的对象实例、数组 → 堆(新生代 Eden 或大对象直入老年代)
- 类结构、static 变量、常量、运行时常量池 → 方法区(Metaspace)
- 每个线程独有计数器、栈帧、本地方法调用 → 线程私有区域
紧扣分代模型,理解对象生命周期
对象不是一出生就进老年代。新对象默认分配在 Eden 区;Eden 满触发 Minor GC,存活对象进入 Survivor(S0/S1 间复制并年龄 +1);达到阈值(如默认 15 次)或 Survivor 放不下,才晋升老年代。大对象(如 10MB 数组)可能直接进老年代,避免在 Survivor 区反复复制。
- 观察 GC 日志里的 “GC (Allocation Failure)” 和 “PSYoungGen” 可验证分配行为
- 用
-Xms512m -Xmx512m -Xmn256m -XX:SurvivorRatio=8固定堆结构,便于复现过程 - 通过
Object o = new byte[10 * 1024 * 1024];触发大对象直入老年代
把 GC 和参数调优落到具体问题上
调优不是盲目改数字,而是响应现象:频繁 Minor GC?可能是 Eden 太小或对象存活率高;老年代增长快?检查是否有内存泄漏或过早晋升;单次 GC 停顿长?考虑换 G1 或 ZGC。关键参数要懂含义:-Xms/-Xmx 控堆总大小,-XX:NewRatio 决定新老代比例,-XX:+UseG1GC 启用 G1 回收器,-XX:+PrintGCDetails 打开日志看实际行为。
- 用 jstat 实时监控:jstat -gc
1000 查看各区内存使用与 GC 频次 - 导出堆转储(jmap -dump:format=b,file=heap.hprof
),用 MAT 分析对象引用链 - 模拟 OOM:加
-XX:+HeapDumpOnOutOfMemoryError自动保存快照
结合 JMM 理清并发下的内存可见性
多线程场景下,“变量修改了但别的线程看不到”,本质是工作内存没及时刷回主内存。volatile 保证每次读都从主内存取、写都立即刷回;synchronized 不仅互斥,也建立 happens-before 关系,确保解锁前所有写对后续加锁线程可见;final 字段在构造结束时写入,对其初始化值有特殊保障。
- 不要只背“volatile 保证可见性”,要明白它禁止指令重排序 + 强制读写主内存
- 对比
int count = 0;和volatile int count = 0;在多线程自增时的行为差异 - 理解为什么双重检查锁里 instance 要加 volatile:防止对象未初始化完成就被其他线程看到
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











