Skip to content

JVM 内存结构与对象创建 ​

S6-007建议学高级约 90 分钟JDK 21Java 高级与架构 · JVM

1. 本节定位 ​

「JVM 把内存分成几块」是面试第一题,但背概念不如看真实日志。

★ 本节用 -Xlog:gc 采集了本项目自己程序的一手日志*:

text
Using G1 | Heap Region Size: 4M
Heap Min: 8M / Initial: 508M / Max: 8116M
GC(0) Pause Young (Normal) (G1 Evacuation Pause)
  Eden regions: 12->0(58)   Survivor: 0->1(2)   Old: 1->1   Humongous: 0->0

2. 学习目标 ​

  • 能画出运行时数据区(堆/栈/元空间/PC)★★★;
  • 能从 G1 日志读出 Region 模型与分代行为 ★★★;
  • 知道 Humongous 大对象的判定与风险 ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

★ 线上 80% 的 JVM 故障只有三种 ✗:OOM(堆/元空间)、GC 停顿过长、栈溢出 —— 看懂内存结构与 GC 日志是处理它们的前提 ✓✓

5. 核心概念 ​

5.1 运行时数据区 ★★★ ​

区域线程放什么OOM/错误
堆共享 ★★★对象实例Java heap space ✗
虚拟机栈私有 ★★栈帧StackOverflowError ✗
本地方法栈私有native 方法的栈帧StackOverflowError ✗
元空间共享 ★★类元数据Metaspace ✗
程序计数器私有字节码行号唯一不会 OOM ✓

★ 一条对应:栈溢出查递归;堆 OOM 查大对象/泄漏;元空间 OOM 查动态类生成 ✓✓✓

5.2 G1 的 Region 模型(实测)★★★ ​

G1 把堆切成 1~32M 的 Region(本机 4M)——Eden/Survivor/Old 不再物理连续,都是 Region 的逻辑角色 ✓✓✓

实测 GC(0):Eden 12 个 Region 清空(12→0),存活对象复制(Evacuate)进 Survivor(0→1)✓✓

区域放什么回收
Eden ★★★新对象Minor GC(频繁、快 ✓)
Survivor ★★Minor GC 存活者晋升 Old 前的缓冲(年龄阈值 15)
Old ★★长期存活对象Mixed/Full GC(慢 ✗)
Humongous ★★★≥ Region 一半的大对象直接 Old,碎片风险 ✗✗

★★★ Humongous 是 G1 经典事故源 ✗✗:本机 Region 4M → 对象 ≥ 2M 即 Humongous ✗ —— 读一个 10MB 文件 = 3 个 Humongous ✗✗ —— 大对象会触发 Full GC 甚至 OOM ✗✗✓ 对策:分块处理、调大 Region(-XX:G1HeapRegionSize)✓✓

5.3 对象创建与分配 ★★ ​

text
类加载检查 → 分配内存(TLAB 上,无锁 ✓)→ 零初始化 → 设对象头 → 执行 <init>

TLAB:每线程在 Eden 私有的一小块,分配只是指针移动,纳秒级 ✓✓✓ —— 「Java 创建对象很贵」这个印象过时了 ✓✓

5.4 堆参数(实测)★★★ ​

Heap Min 8M / Initial 508M / Max 8116M ✓ —— 生产建议 Initial = Max(-Xms = -Xmx,避免运行时扩容抖动)✓✓✓; Region Size 随堆自动算(8G 堆 → 4M ✓)

6. 递进示例 ​

6.1 看自己程序的堆 ​

powershell
java "-Xlog:gc*" -cp java-deep/target/classes cn.kb.learn.deep.jvm.GcLesson

6.2 调大 Region 防 Humongous ​

powershell
java "-XX:G1HeapRegionSize=16m" ...

6.3 反例:一次性读整个文件 ​

java
byte[] all = Files.readAllBytes(Path.of("big.csv"));   // ✗✗ 10MB = Humongous

7. 完整可运行代码与实测 ​

代码:labs/java-deep/src/main/java/cn/kb/learn/deep/jvm/GcLesson.java(100 轮 × 1MB 短命对象)

实测输出(关键部分):

text
[0.091s] Using G1
[0.103s] Heap Region Size: 4M
[0.104s] Heap Min Capacity: 8M | Initial Capacity: 508M | Max Capacity: 8116M
[0.206s] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[0.212s] GC(0) Eden regions: 12->0(58) | Survivor: 0->1(2) | Old: 1->1

★ 100 轮 × 1MB 短命对象 = 一次 Young GC 完成 ✓ —— 这就是「分代假说:绝大多数对象朝生夕死」的直接证据 ✓✓✓

8. Debug 观察点 ​

  1. 循环轮数 ×10:观察 GC 次数与 Pause ✓✓;
  2. 分配 10MB byte[]:观察 Humongous 出现 ✗✓✓;
  3. -Xmx512m 再跑:观察堆缩容后 GC 更频繁 ✓;
  4. 无限递归:观察 StackOverflowError ✓。

9. 常见错误 ​

#错误说明
1以为创建对象很贵 ✗TLAB 纳秒级 ✓✓
2一次性读大文件 ✗✗Humongous ✗✗
3-Xms ≠ -Xmx ✗扩容抖动 ✓
4元空间 OOM 去查堆 ✗✗查动态类生成 ✓
5StackOverflow 查堆 ✗✗查递归与栈深 ✓✓

10. 动手实验 ​

  1. 跑 GcLesson 并读日志 ✓✓;
  2. 分配 10MB 对象触发 Humongous ✗✓✓;
  3. -Xmx256m -Xlog:gc*:观察 Full GC 与 OOM ✗✓✓;
  4. jcmd GC.heap_info 看运行时堆 ✓。

验收方式:能对着一份 GC 日志说出各代行为与是否健康。

扩展任务:制造一次元空间 OOM(CGlib 生成大量类),记录日志并写分析 ✓✓

11. 练习题 ​

  1. 堆/栈/元空间各自放什么?各抛什么异常?
  2. G1 的 Humongous 判定标准?本机 Region 4M 时多大算?
  3. 为什么「Java 对象分配很贵」是过时印象?
  4. 生产环境 -Xms 应该与 -Xmx 相等吗?为什么?
  5. GC(0) Eden 12->0(58) 的三个数字分别是什么?
参考答案(要点)
  1. 堆放对象(OOM: heap space);栈放栈帧(StackOverflowError);元空间放类元数据(Metaspace OOM) ✓✓★
  2. 对象 ≥ Region 一半;本机 ≥ 2MB ✓✓★
  3. TLAB + bump-the-pointer,纳秒级 ✓✓★
  4. 建议相等:避免运行时扩容/收缩的停顿与抖动 ✓✓★
  5. GC 前 12 个 Eden Region → GC 后 0 个(下一轮上限 58 个)✓✓★

12. 掌握标准 ​

  • [ ] 能画出运行时数据区并对应 OOM 类型 ★★★;
  • [ ] 能从 G1 日志读出 Region 模型与分代行为 ★★★;
  • [ ] 知道 Humongous 判定与风险 ★★★;
  • [ ] 知道 TLAB 与对象创建成本 ★★★;
  • [ ] 知道生产 -Xms = -Xmx 的理由 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《类加载机制》。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引