内存与 GC
约 947 字大约 3 分钟
布欧-Lewyon
2026-05-16
JVM 的内存管理由两个层面构成:运行时数据区(如何划分内存空间)和垃圾回收(GC)(如何自动回收不再使用的对象)。理解这两部分有助于排查 OOM、调优 GC 停顿。
运行时数据区
┌─────────────────────────────────────┐
│ 堆(Heap) │ ← 所有线程共享,存对象实例
│ ┌──────────┬──────────┬──────────┐ │
│ │ 新生代 │ 老年代 │ 字符串常量池等 │
│ │ (Young) │ (Old) │ │
│ └──────────┴──────────┴──────────┘ │
├─────────────────────────────────────┤
│ 元空间(Metaspace) │ ← 类元数据、方法字节码
├─────────────────────────────────────┤
│ 虚拟机栈(VM Stack) │ ← 每个线程私有,存栈帧
│ 本地方法栈(Native Stack) │ ← native 方法调用
│ 程序计数器(PC Register) │ ← 当前执行指令地址
└─────────────────────────────────────┘堆(Heap)
所有 new 出来的对象都在堆上分配,是 GC 管理的主要区域。堆分为两个逻辑区域:
- 新生代(Young Generation):新对象分配在这里,GC 频率高、速度快。进一步分为 Eden 和两个 Survivor 区(S0/S1)。
- 老年代(Old Generation):经过多次 GC 仍存活的对象晋升到此区域,GC 频率低但耗时较长。
虚拟机栈
每个线程创建时分配一个栈,每个方法调用对应一个栈帧。栈帧存储局部变量表、操作数栈、动态链接、方法出口。栈深度超过限制时抛出 StackOverflowError。
元空间(Metaspace)
Java 8 起替代永久代(PermGen),存储类的元数据、方法字节码、常量池等。元空间使用本地内存(而非堆内存),默认无上限(可通过 -XX:MaxMetaspaceSize 限制)。
分代 GC 直觉
JVM 基于"大部分对象很快消亡"的假设,采用分代收集策略:
对象分配 → Eden
├── Minor GC 后存活 → 复制到 S0
├── 再次 Minor GC 存活 → 复制到 S1
└── 达到阈值(默认 15 次)→ 晋升到老年代- Minor GC(Young GC):清理新生代,频繁但速度快(STW 时间短)。
- Major GC / Full GC:清理老年代(通常也连带清理新生代),STW 时间较长,应尽量避免。
- STW(Stop-The-World):GC 发生时所有应用线程暂停。
常见 GC 收集器
| 收集器 | 适用区域 | 特点 |
|---|---|---|
| Serial | 新生代 | 单线程,适合客户端、小内存 |
| Parallel | 新生代 | 多线程并行,吞吐量优先(Java 8 默认) |
| CMS | 老年代 | 低停顿,已标记废弃 |
| G1 | 全堆 | 区域化分代,可预测停顿(JDK 9+ 默认) |
| ZGC(JDK 11+) | 全堆 | 超低停顿(< 2ms),大堆场景 |
JDK 17+ 的默认收集器是 G1。G1 将堆划分为多个 Region,优先回收垃圾最多的 Region(Garbage-First),从而控制停顿时间。
常见的 JVM 参数
-Xms512m # 初始堆大小
-Xmx2g # 最大堆大小
-Xss256k # 线程栈大小
-XX:+UseG1GC # 使用 G1 收集器
-XX:MaxGCPauseMillis=200 # G1 目标停顿时间
-XX:MetaspaceSize=128m # 元空间初始大小
-XX:+PrintGCDetails # 打印 GC 详情(JDK 8)
-Xlog:gc* # GC 日志(JDK 9+)
-XX:+HeapDumpOnOutOfMemoryError # OOM 时自动 dump 堆
-XX:HeapDumpPath=/path/to/dump # dump 文件路径小结
- 堆分新生代(Eden + Survivor)和老年代,对象从新生代逐步晋升。
- 虚拟机栈每方法调用分配一个栈帧,递归过深导致
StackOverflowError。 - 元空间(Java 8+)替代永久代,存储类元数据,使用本地内存。
- JDK 17+ 默认 G1 收集器,可预测停顿时间。
- 易错:
-Xms和-Xmx设为不同值时,堆会在两者之间动态伸缩,影响性能。生产环境建议将两者设为相同值,避免运行期扩展开销。另外,StackOverflowError是Error而非Exception,try-catch无法恢复。 - 思考任务:在应用启动参数中加入
-Xlog:gc* -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump,运行一个会 OOM 的测试程序,观察 GC 日志。
上一节:并发问题与排查
下一节:类加载与反射
