1093 字
约 3 分钟
9
S05-J04 · JVM 运行时数据区与垃圾回收基础
2024-12-31
2026-11-07

适用读者:被"堆/栈/方法区/元空间"绕晕的人。
前置:S04-J03(字节码)。
实测环境:OpenJDK 21.0.12.1(GC 日志实测,2026-11-05)。
覆盖旧文:A-2575(1238) A-1644(393) A-1130(208) A-2334(363) + 1641(88) 2590(260) 1023(86) 1632(155) 1636(134) 1628(116) 1627(142) 1629(174) 1550(137) 1544(75) 1128(126) 1010(63) = 3,758 点。
覆盖扩展:E-S05-011(栈帧结构)、E-S05-012(可达性分析)。
上一篇:S05-J03 · 异步编排 CompletableFuture
下一篇:S05-J05 · 类加载与字节码执行。


1. 一句话结论

JVM 的内存分五块:方法区(元空间)、堆、虚拟机栈、本地方法栈、程序计数器。GC 只关心"堆"(以及元空间的类卸载)。

区域 线程共享 存什么 会 OOM 吗
堆 ✅ 对象实例 ✅ OutOfMemoryError: Java heap space
方法区/元空间 ✅ 类元数据、常量 ✅ Metaspace
虚拟机栈 ❌ 栈帧(局部变量、操作数栈) ✅ StackOverflowError
本地方法栈 ❌ native 方法 ✅
程序计数器 ❌ 当前字节码行号 ❌(唯一不会 OOM)

2. 实测:GC 日志长什么样

$ java -Xmx128m -Xlog:gc:stdout G

实测输出(G1):

[0.015s][info][gc] Using G1
[0.629s][info][gc] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 56M->55M(128M) 14.651ms
[0.646s][info][gc] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 60M->60M(128M) 4.082ms
[0.656s][info][gc] GC(3) Pause Remark 65M->65M(128M) 1.514ms
[0.665s][info][gc] GC(3) Concurrent Mark Cycle 18.793ms
[0.786s][info][gc] GC(4) Pause Young (Prepare Mixed) (G1 Humongous Allocation) 125M->65M(128M) 3.998ms
[0.845s][info][gc] GC(5) Pause Young (Mixed) (G1 Humongous Allocation) (Evacuation Failure) 125M->70M(128M) 5.213ms

逐段读法:

片段 含义
Pause Young 年轻代 GC(STW)
56M->55M(128M) 回收前→回收后(堆上限 128M)
14.651ms 停顿时间
Humongous Allocation 分配了大对象(> Region 一半,8MB 数组就是)
Concurrent Mark Cycle 并发标记(不 STW)
Pause Mixed 混合回收(年轻代 + 部分老年代)
Evacuation Failure 晋升失败(目标区放不下)——这是要警惕的信号

Evacuation Failure 的教学价值:它说明"堆给小了 + 大对象多"会导致回收失败,进而 Full GC。调优的第一信号就是这个。


3. 对象分配与栈帧(E-S05-011)

概念 说明
TLAB 每个线程的私有分配缓冲区(减少竞争)
对象头 Mark Word(哈希/锁)+ 类型指针
栈帧 局部变量表 + 操作数栈 + 动态链接 + 返回地址
逃逸分析 对象不出方法 → 可能栈上分配/标量替换(不占堆)

推论:"new 出来的对象一定在堆上"不完全对——JIT 可能优化掉。


4. 对象存活判定(E-S05-012)

方法 说明
引用计数 简单但有循环引用问题 → JVM 不用
可达性分析 从 GC Roots 出发不可达即可回收

GC Roots 包括:栈中局部变量、静态字段、常量、JNI 引用、活动线程。

四种引用:

引用 回收时机
强引用 永不(直到不可达)
SoftReference 内存不足时
WeakReference 下次 GC(ThreadLocal 的 key 就是它)
PhantomReference 回收前通知(用于清理)

5. 五个误区

误区 真相
"程序计数器也会 OOM" 唯一不会
"对象一定分配在堆上" 逃逸分析可能栈上分配
"引用计数是 JVM 的 GC 算法" JVM 用可达性分析
"GC 日志只有停顿时间有用" Evacuation Failure/晋升失败更关键
"-Xmx 越大越好" 堆太大会拉长单次回收(G1 可分 Region 缓解)

6. 自检

  1. 五个运行时区域里,哪些线程共享?哪个不会 OOM?
  2. GC 日志 56M->55M(128M) 14.651ms 各段含义?
  3. Evacuation Failure 说明什么?会导致什么?
  4. 为什么 JVM 不用引用计数?
  5. 四种引用分别什么时候回收?

7. 下一篇

S05-J05 · 类加载与字节码执行

内存区讲完,下一篇讲"类怎么进来":双亲委派、三个阶段,以及为什么它会被"破坏"。


本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-2575/1644/1130/2334/1641/2590/1023/1632/1636/1628/1627/1629/1550/1544/1128/1010;扩展点 E-S05-011/012。GC 日志为 OpenJDK 21 实测。

S05-J04 · JVM 运行时数据区与垃圾回收基础
https://www.clxhxhhr.top/posts/5250/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。