Appearance
JVM(Java Virtual Machine)
JVM 流程
- 1.创建 JVM 实例(JNI_CreateJavaVM)
- 2.初始化类加载器(Bootstrap → Ext → App)
- 3.加载核心类(java.lang.Object, System, Thread 等)
- 4.执行 main() 方法
- 5.运行时:解释执行 → JIT编译 → GC回收 → 程序结束
- 6.销毁 JVM 实例(DestroyJavaVM)
JVM 结构
plain
┌───────────────────────────────────────────────────────────┐
│ Class Loader Subsystem │
│ ┌─────────┐ ┌────────────┐ ┌───────────┐ ┌──────────┐ │
│ │ Loading │ │Verification│ │Preparation│ │Resolution│ │
│ └─────────┘ └────────────┘ └───────────┘ └──────────┘ │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ Runtime Data Areas │
│ ┌───────────┐ ┌─────────┐ ┌─────────┐ ┌────────────┐ │
│ │Method Area│ │ Heap │ │VM Stack │ │Native Stack│ │
│ │Metaspace │ │ │ │ │ │ │ │
│ └───────────┘ └─────────┘ └─────────┘ └────────────┘ │
│ ┌─────────┐ │
│ │ PC │ │
│ └─────────┘ │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ Execution Engine │
│ ┌─────────────┐ ┌───────────────┐ ┌──────────────┐ │
│ │ Interpreter │ │ JIT (C1/C2) │ │ GC │ │
│ └─────────────┘ └───────────────┘ └──────────────┘ │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ JNI / Native Method │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ JNI │ │ Native Method │ │
│ └─────────────┘ └──────────────────┘ │
└───────────────────────────────────────────────────────────┘Class Loader Subsystem
加载流程
plain
源代码(.java) → 编译 → 字节码(.class) → 类加载器 → JVM 内存Runtime Data Areas
plain
┌─────────────────────────────────────────────┐
│ Runtime Data Areas │
│ ┌─────────────────────────────────────────┐ │
│ │ Thread-Private Areas │ │
│ │ ┌─────────┐ ┌─────────┐ ┌────────────┐ │ │
│ │ │ PC │ │VM Stack │ │Native Stack│ │ │
│ │ └─────────┘ └─────────┘ └────────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ Thread-shared Areas │ │
│ │ ┌─────────────────────┐ ┌───────┐ │ │
│ │ │Method Area/Metaspace│ │ Heap │ │ │
│ │ └─────────────────────┘ └───────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘| 区域 | 线程 | 作用 | 特点 | |
|---|---|---|---|---|
| PC | 程序计数器 | 私有 | 记录当前线程执行的字节码行号 | 唯一不会发生 OutOfMemory 的区域 |
| VM Stack | 虚拟机栈 | 私有 | 存储栈帧(局部变量、操作数栈、动态链接等) | 方法调用和返回,StackOverflowError / OutOfMemory |
| Native Stack | 本地方法栈 | 私有 | 为 Native 方法服务 | 与虚拟机栈类似 |
| Heap | 堆 | 共享 | 存放对象实例和数组 | GC 主要区域,分代收集 |
| Method Area/Metaspace | 方法区 | 共享 | 存储类信息、常量、静态变量、即时编译器编译后的代码 | JDK 8 前:永久代;JDK 8+:元空间(本地内存) |
VM PC
每个线程有独有一个程序计数器,与线程生命周期相同,记录当前线程执行的字节码行号。
当前线程执行的字节码行号指示器,储存方法区字节码指令的地址偏移量,随线程创建而创建销毁而销毁。是实现线程切换、分支跳转、循环控制的关键。
执行 Native 方法时 PC 为 Undefined。Native 方法由操作系统直接执行,使用物理 CPU 的程序计数器(RIP/EIP),JVM 无法也不需跟踪本地代码的执行位置,返回 Java 代码时,JVM 从返回地址恢复 PC。
Metaspace
| 特性 | PermGen(JDK 7-) | Metaspace(JDK 8+) |
|---|---|---|
| 位置 | JVM 堆内部 | 本地内存(Native Memory) |
| 大小限制 | 固定上限(-XX:MaxPermSize) | 默认无上限,受限于物理内存 |
| GC 影响 | 触发 Full GC 清理 | 独立回收,不强制 Full GC |
| OOM 类型 | OutOfMemoryError: PermGen space | OutOfMemoryError: Metaspace |
| 存储内容 | 类元数据、常量池、静态变量 | 仅类元数据(Class Metadata) |
| 内存碎片 | 易产生碎片 | 使用内存池管理,碎片少 |
结构
plain
┌─────────────────────────────────────────┐
│ 本地内存 (Native Memory) │
├─────────────────────────────────────────┤
│ Metaspace │
│ ┌─────────────────────────────────┐ │
│ │ Class Metadata │ │
│ │ • Klass 结构(类描述符) │ │
│ │ • 方法元数据(字节码、参数) │ │
│ │ • 常量池(运行时常量池) │ │
│ │ • 字段数据、注解 │ │
│ │ • OOP 映射(对象-类关系) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Vtables / Itables │ │
│ │ (虚方法表、接口方法表) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ 内存管理结构 │ │
│ │ • VirtualSpaceList(虚拟空间链表) │ │
│ │ • Chunk 内存池(按大小分级) │ │
│ │ • ClassLoaderData 隔离区 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘| 参数 | 说明 | 默认值/建议 |
|---|---|---|
-XX:MetaspaceSize | 初始大小 | 约 20MB(动态调整) |
-XX:MaxMetaspaceSize | 最大上限 | 无限制(建议设置) |
-XX:MinMetaspaceFreeRatio | GC 后最小空闲比例 | 40% |
-XX:MaxMetaspaceFreeRatio | GC 后最大空闲比例 | 70% |
-XX:CompressedClassSpaceSize | 压缩类指针空间 | 1GB(上限) |
Chunk 分级池
分配策略:
- 按需求大小匹配对应 Chunk 链表
- 无可用 Chunk 时从 VirtualSpaceNode 切割
- Node 不足时向 OS 申请新内存(
malloc/mmap)
plain
┌────────────────────────────────────────┐
│ VirtualSpaceNode │
│ (从 OS 申请的大块内存,如 2MB/4MB) │
├────────────────────────────────────────┤
│ ┌────────┬────────┬────────┬───────┐ │
│ │ Chunk │ Chunk │ Chunk │ ... │ │
│ │ 4KB │ 8KB │ 16KB │ │ │
│ └────────┴────────┴────────┴───────┘ │
│ ↑ ↑ ↑ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 4KB │ │ 8KB │ │ 16KB │ ... │
│ │ 链表 │ │ 链表 │ │ 链表 │ │
│ └──────┘ └──────┘ └──────┘ │
│ 按大小分级缓存,减少碎片 │
└────────────────────────────────────────┘ClassLoader 隔离与回收
回收条件:
- 该类加载器加载的所有类实例已死
- 该类加载器本身无引用
- 触发条件:Metaspace 占用达到
MetaspaceSize阈值
plain
┌─────────────────────────────────────────┐
│ Bootstrap ClassLoader 数据区 │
│ (核心类库:rt.jar,无回收) │
├─────────────────────────────────────────┤
│ Application ClassLoader 数据区 │
│ (应用类,GC 时检查卸载) │
├─────────────────────────────────────────┤
│ Custom ClassLoader 数据区 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Loader1 │ │ Loader2 │ │ Loader3 │ │
│ │ 元数据 │ │ 元数据 │ │ 元数据 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ↑ 当 ClassLoader 实例被 GC 回收时 │
│ ↑ 对应 Metaspace 区域一并释放 │
└─────────────────────────────────────────┘metaspace-oomException in thread "main" java.lang.OutOfMemoryError: Metaspace
java
public class MetaspaceLeak {
static void leak() {
while (true) {
// 每次创建新的 ClassLoader 加载类
URLClassLoader loader = new URLClassLoader(urls);
Class<?> clazz = loader.loadClass("GeneratedClass");
// 未关闭 loader,类元数据累积在 Metaspace
}
}
}Heap
- 作用:存放所有对象实例和数组
- 线程共享:所有线程共享,需同步机制
- 垃圾回收:GC主要工作区域
- OOM:堆内存不足时抛出
OutOfMemoryError
plain
┌───────────────────────────────────────────────────────┐
│ (Heap) │
├───────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────┐ ┌──────────┐ │
│ │ Young Gen │ │ Old Gen │ │
│ │ 1/3 │ │ 2/3 │ │
│ │ ┌─────────┬───────────┬───────────┐ │ │ │ │
│ │ │ Eden │ S0 │ S1 │ │ │ │ │
│ │ │ 8/10 │ 1/10 │ 1/10 │ │ │ │ │
│ │ └─────────┴───────────┴───────────┘ │ │ │ │
│ └──────────────────────────────────────┘ └──────────┘ │
└───────────────────────────────────────────────────────┘- Young Gen
- Eden:新对象分配区
- survivor0(S0):存活对象复制区,交替使用
- S1:同上
- Old Gen
- 存放长期存活对象(默认晋升阈值:15)
- 大对象直接进入
分配内存流程
plain
1. 新对象 → 尝试在Eden分配
↓ 空间不足
2. Minor GC → 存活对象移入Survivor区(S0或S1)
↓ 对象年龄增长
3. 年龄达阈值(默认15)→ 晋升老年代
↓ 老年代空间不足
4. Major GC/Full GC → 清理老年代
↓ 仍不足
5. 抛出 OutOfMemoryError: Java heap spaceJVM 参数
| 参数 | 说明 | default | 示例 |
|---|---|---|---|
-Xms | 初始堆大小 | m * 1/64 | -Xms512m |
-Xmx | 最大堆大小 | m * 1/4 | -Xmx2g |
-Xmn | 年轻代大小 | `` | -Xmn256m |
-XX:NewRatio | 老年代/年轻代比例 | 3/1 | -XX:NewRatio=2(老:新=2:1) |
-XX:SurvivorRatio | Eden/Survivor比例 | 8/2 | -XX:SurvivorRatio=8 |
-Xss | 栈内存大小,影响函数调用深度。例如,1024KB 大约7625次,512 KB 大约3707次 | linux x64, 1024KB | |
-XX:+HeapDumpOnOutOfMemoryError | OOM时生成堆转储 | 调试用 |
Heap-OOM :java.lang.OutOfMemoryError: Java heap space
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 内存泄漏 | 堆持续增长,GC无法回收 | 分析Heap Dump,查找未释放引用 |
| 大对象过多 | 频繁Full GC | 优化对象生命周期,使用对象池 |
| 年轻代过小 | 频繁Minor GC | 调大 -Xmn 或调整比例 |
| 老年代膨胀 | Full GC频繁 | 检查静态集合缓存,优化晋升策略 |
监控与诊断命令
| 命令 | 用途 |
|---|---|
jstat -gc <pid> | |
jmap -heap <pid> |
VM Stack
- 线程私有:每个线程独立栈,生命周期与线程相同
- 方法执行:存储栈帧(Stack Frame),对应方法调用
- LIFO结构:后进先出,方法结束自动弹栈
- StackOverflowError:栈深度超过限制(如无限递归
- OOM:扩展时无法申请足够内存(较少见)
结构 HotSpot 实现:两栈物理合并,逻辑分离,统一由 -Xss 控制。
plain
┌───────────────────────────────────────┐
│ Java Thread │
├───────────────────────────────────────┤
│ JVM Stack │
│ ┌───────────────────────────────────┐ │
│ │ Stack Frame1 │ Stack Frame2 │ ... │ │
│ └───────────────────────────────────┘ │
├───────────────────────────────────────┤
│ Native Method Stack │
│ ┌───────────────────────────────────┐ │
│ │ Stack Frame1 │ Stack Frame2 │ ... │ │
│ └───────────────────────────────────┘ │
└───────────────────────────────────────┘栈帧(Stack Frame)结构
plain
┌─────────────────┐
│ Local Variables │
├─────────────────┤
│ Operand Stack │
├─────────────────┤
│ Dynamic Linking │
├─────────────────┤
│ Return Address │
├─────────────────┤
│ Extra │
└─────────────────┘- 局部变量(Local Variables):基本数据类型、对象引用、returnAddress。(Slot为单位(32位),long/double占2 Slot)
- 操作数栈(Operand Stack):字节码指令工作区,数据运算临时存储
- 动态连接(Dynamic Linking):指向运行时常量池的方法引用
- 返回地址(Retuen Address):方法执行完毕后的返回位置
- 附加信息(Extra):调试信息、异常处理表等(可选)
常见问题
栈溢出Exception in thread "main" java.lang.StackOverflowError
java
// 栈帧无限创建,直至溢出
public void recursive() {
recursive();
}内存溢出java.lang.OutOfMemoryError: Out of stack space
java
// -Xss2m 时,2500个线程 ≈ 5GB内存
while(true) {
new Thread(() -> { sleep(1000); }).start();
}局部变量表槽位复用
java
public void method() {
{
byte[] big = new byte[6 * 1024 * 1024]; // 6MB数组
} // big作用域结束,但栈帧未结束,Slot可能未复用
int a = 0; // 若编译器优化,复用big的Slot,数组可被GC
System.gc(); // 效果取决于编译器实现
}监控与诊断命令
| 命令 | 用途 |
|---|---|
jcmd <pid> VM.metaspace | 查看 Metaspace 详细统计 |
jcmd <pid> GC.class_stats | 类加载统计(JDK 8u40+) |
jstat -class <pid> | 类加载/卸载数量监控 |
arthas / visualvm | 可视化 Metaspace 趋势 |
Native Stack
JVM 参数
sh
# 堆内存
-Xms512m # 初始堆大小
-Xmx2g # 最大堆大小
-Xmn256m # 年轻代大小
# 元空间(JDK 8+)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
# 栈内存
-Xss1m # 每个线程栈大小
# GC 相关
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryErrorJMM, Java Memory Model
抽象规范,定义多线程环境下共享变量的访问规则
- 原子性:操作不可中断(
synchronized、Lock、Atomic) - 可见性:一个线程修改对另一个线程可见(
volatile、synchronized、final) - 有序性:禁止指令重排序(
volatile、happens-before原则)