主题
JVM 技术栈总览
蒸馏自
B40-资源/语雀-Java开发/JVM/(11 篇:内存区域 73KB + 类加载 + Class 文件结构 + 深入拆解 Java 虚拟机 7 篇)与详解JVM逃逸分析。本页是 JVM 域的全局地图,细节见各概念卡。
一句话机制
JVM 把 Java 字节码翻译成机器码执行,中间靠「内存区域」管理数据、靠「类加载」装载类型、靠「GC」回收垃圾、靠「JIT + 逃逸分析」做运行时优化。 理解 JVM = 理解这四条主线的协作。
JVM 全景四主线
| 主线 | 核心问题 | 关键机制 | 详情页 |
|---|---|---|---|
| 运行时内存 | 数据放哪、怎么访问 | 程序计数器/虚拟机栈/堆/方法区/直接内存;对象三段式布局 | 概念卡片:JVM内存区域与对象模型 |
| 类加载 | .class 怎么变成可用类型 | 7 阶段生命周期;双亲委派;<clinit> | 概念卡片:类加载机制与双亲委派 |
| 垃圾回收 | 谁回收、何时回收 | 分代(Eden:S0:S1=8:1:1);Minor/Major/Full GC | 概念卡片:JVM内存区域与对象模型 |
| 即时编译 | 热点代码怎么变快 | JIT;逃逸分析→栈上分配/标量替换/锁消除 | 见下方「JIT 与逃逸分析」 |
运行时数据区速览
| 区域 | 线程 | 存什么 | 异常 |
|---|---|---|---|
| 程序计数器 | 私有 | 当前字节码指令地址(Native 方法时为 Undefined) | 唯一不 OOM |
| 虚拟机栈 | 私有 | 栈帧(局部变量表/操作数栈/动态链接/返回地址) | StackOverflowError / OOM |
| 本地方法栈 | 私有 | native 方法调用 | 同上 |
| 堆 | 共享 | 对象实例 + 数组(GC 主战场) | OOM: heap |
| 方法区 | 共享 | 类型信息/常量/静态变量/JIT 代码缓存 | OOM: Metaspace |
| 直接内存 | — | NIO DirectByteBuffer(堆外) | OOM |
JDK8 分水岭:方法区实现由「永久代」(堆内)换成「元空间 Metaspace」(本地内存);字符串常量池 + 静态变量在 JDK7 已移入堆。
类加载一句话链路
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载(验证+准备+解析统称「连接」)。核心是「双亲委派」:类加载请求先交给父加载器,父加载不了才自己来,保证 java.lang.String 之类核心类唯一且不被篡改。<clinit>() 是类的初始化方法(static 块 + 静态变量赋值合并而成),JVM 保证它多线程安全,但因此耗时操作可能引发隐蔽死锁。
GC 分代一句话
堆分新生代(Eden + 两个 Survivor,默认 8:1:1)与老年代。对象优先在 Eden 分配,熬过 15 次 Minor GC(-XX:MaxTenuringThreshold)晋升老年代。分代的唯一理由是优化 GC 性能——70%~99% 的对象是朝生夕死的临时对象,分开回收能避免每次全堆扫描。Minor GC 频繁但快(STW),Major/Full GC 慢 10 倍以上、能避免就避免。
JIT 与逃逸分析
JIT 编译器把热点字节码编译成机器码。逃逸分析是 JIT 的关键优化——判断对象是否逃出当前方法/线程:
| 逃逸级别 | 定义 | 锁消除 | 标量替换 |
|---|---|---|---|
| 不逃逸 NoEscape | 对象只在方法内部流转 | ✅ | ✅ |
| 方法逃逸 ArgEscape | 传给其他方法/被返回 | ✅ | ❌ |
| 线程逃逸 GlobalEscape | 赋给类变量/实例字段 | ❌ | ❌ |
未逃逸对象可做三件事:栈上分配(随栈帧销毁,不占堆)、标量替换(对象拆成成员变量,压根不建对象)、同步消除(移除无意义的锁)。
实测(源文数据):1 亿次循环内
new一个不逃逸对象,开启逃逸分析 3ms / 堆上仅 11 万个对象;关闭后 400ms / 3 千万个对象。差距 100 倍量级。
常见误解(避坑)
- ❌ "JVM 内存只有堆和栈"。→ 漏了方法区/程序计数器/直接内存;且「栈」是线程私有、「堆」线程共享,二者职责不同(栈管执行、堆管存储、方法区管类型信息)。
- ❌ "方法区 = 永久代"。→ 永久代只是 HotSpot 在 JDK7 前对方法区的一种实现;JDK8 起换成元空间(本地内存),其他 JVM(JRockit/J9)根本没有永久代概念。
- ❌ "对象一定分配在堆上"。→ 逃逸分析后未逃逸对象可栈上分配/标量替换,JIT 可能压根不在堆上建它。
- ❌ "类初始化 = 构造方法"。→ 类的初始化是
<clinit>()(管 static),对象的初始化是<init>()(管实例字段+构造),二者是两码事。
关联
- 原始资料:✅【五】Java内存区域 · ✅【三】类加载机制 · ✅【一】Class类文件结构详解 · 详解JVM逃逸分析(EscapeAnalysis)
- 概念卡:概念卡片:JVM内存区域与对象模型 · 概念卡片:类加载机制与双亲委派
- 域地图:A00-百科/Java后端/Java后端