Skip to content

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 倍量级。

常见误解(避坑)

  1. ❌ "JVM 内存只有堆和栈"。→ 漏了方法区/程序计数器/直接内存;且「栈」是线程私有、「堆」线程共享,二者职责不同(栈管执行、堆管存储、方法区管类型信息)。
  2. ❌ "方法区 = 永久代"。→ 永久代只是 HotSpot 在 JDK7 前对方法区的一种实现;JDK8 起换成元空间(本地内存),其他 JVM(JRockit/J9)根本没有永久代概念。
  3. ❌ "对象一定分配在堆上"。→ 逃逸分析后未逃逸对象可栈上分配/标量替换,JIT 可能压根不在堆上建它。
  4. ❌ "类初始化 = 构造方法"。→ 类的初始化是 <clinit>()(管 static),对象的初始化是 <init>()(管实例字段+构造),二者是两码事。

关联

最近更新