vault backup: 2026-08-09 19:06:40

This commit is contained in:
2026-08-09 19:06:40 +08:00
parent e2975eb86b
commit 9d664545c9
46 changed files with 7287 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
---
tags: [test/review, java, jvm-memory, heap, metaspace, oom]
create time: 2026-08-09 12:00
---
# JVM 内存模型_测试题
## 概述
本测试覆盖 JVM 运行时数据区的划分、对象创建流程、GC Roots 判定标准以及常见 OOM 类型,共 10 道题目(6 道选择题 + 3 道填空题 + 1 道简答题),由浅入深检验对 JVM 内存模型的掌握程度。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
以下哪个区域是 JVM 中**唯一不会抛出 OutOfMemoryError** 的内存区域?
A. 堆(Heap)
B. 方法区 / 元空间(Method Area / Metaspace)
C. Java 虚拟机栈(JVM Stack)
D. 程序计数器(Program Counter Register)
### Q2(基础)— 考察行为判断
JDK 8 移除了永久代(PermGen),改用元空间(Metaspace)。关于这一变化,以下说法**正确**的是:
A. 元空间仍然在 JVM 堆内分配内存,只是改了一个名字
B. 元空间使用本地内存(Direct Memory),上限通过 `-XX:MaxMetaspaceSize` 控制
C. 元空间的大小固定不变,不会受到本机总内存的限制
D. 永久代和元空间的 OOM 错误完全相同,都是 `java.lang.OutOfMemoryError: PermGen space`
### Q3(进阶)— 考察核心原理
在堆中通过 `new` 指令创建一个对象时,以下步骤的**正确执行顺序**是:
① 设置对象头(Hash Code、分代年龄、锁标志等)
② 类加载检查(常量池定位 + 必要的类加载)
③ 初始化零值(内存清零)
④ 执行 `<init>` 方法(字段赋初值)
⑤ 分配内存(指针碰撞或空闲列表)
A. ② → ⑤ → ③ → ① → ④
B. ② → ③ → ⑤ → ① → ④
C. ⑤ → ② → ③ → ① → ④
D. ② → ⑤ → ① → ③ → ④
### Q4(进阶)— 考察比较与辨析
标记-复制算法和标记-整理算法都用于解决垃圾回收,两者的关键区别在于:
A. 标记-复制会产生内存碎片,标记-整理不会产生
B. 标记-复制需要将存活对象复制到另一块内存区域并清空原区,标记-整理是让存活对象向一端移动后清理边界外内存
C. 标记-复制只适用于老年代,标记-整理只适用于新生代
D. 标记-整理的 STW 时间一定比标记-复制长
### Q5(深入)— 考察场景推理
某服务的线上日志频繁出现 `java.lang.OutOfMemoryError: GC overhead limit exceeded`,以下排查方向**最不合理**的是:
A. 通过 `-XX:+HeapDumpOnOutOfMemoryError` 自动 dump 堆快照,用 MAT 分析 Dominator Tree
B. 加大堆容量(增大 `-Xmx`)或修复潜在的内存泄漏
C. 通过 `-XX:-UseGCOverheadLimit` 关闭该检查以避免再次报错
D. 检查是否有一群几乎不会死亡的对象长期占用少量堆空间
### Q6(深入)— 考察源码级理解
当 JVM 抛出 `java.lang.OutOfMemoryError: unable to create new native thread` 时,根本原因通常不是 JVM 堆内存不足,而是:
A. 元空间中加载的 Class 数量超过了 `-XX:MaxMetaspaceSize`
B. 操作系统级别的线程数限制触顶(如 Linux 的 `ulimit -u`)
C. Direct ByteBuffer 占用了过多的堆外内存
D. 程序计数器的缓冲区被写满
---
## 二、填空题(3道)
### F1 — 填空1
在使用 Eclipse MAT 分析 OOM 堆快照时,**Dominator Tree** 展示了对象之间的引用关系链。按照对象占用堆空间从大到小排列,排在最上方的通常是__(1)__持有大量引用的__(2)__。
> **提示**: 参考文中提到的"秋招面试高频问题"表格中 OOM 排查流程,以及堆快照分析的典型输出结构。
### F2 — 填空2
一个对象的死亡通常需要两次标记过程:第一次确认没有 GC Roots 路径可达;第二次才是真正回收。如果该对象重写了 `finalize()` 方法且尚未执行过,虚拟机会在第二次标记时帮它触发一次 finalize。**但这个方法在 Java __(1)__ 版本之后已被标记为废弃**。
> **提示**: 回忆文中关于 GC Roots 判定标准部分的提示内容。
### F3 — 填空3
堆大小由两个 JVM 参数控制:`-Xms` 表示__(1)__,`-Xmx` 表示__(2)__。生产环境中通常建议将两者设为同一值,以避免运行时动态扩缩带来的性能开销。
> **提示**: 这是堆内存调优中最基础也是最常见的两个参数。
---
## 三、简答题(1道)
### S1
某电商服务在线上遇到 `java.lang.OutOfMemoryError: Metaspace` 异常。经初步调查发现:
- 该服务大量使用了 CGLIB 动态代理生成子类
- MyBatis 扫描了较大的包路径,导致加载了大量 Class
- 使用了 Spring Boot DevTools 进行热部署
请结合 JVM 内存模型的知识,回答以下问题:
1. 为什么 Metaspace 会耗尽?它与 JDK 7 的永久代有什么区别?
2. 可以从哪些维度(至少 3 个)来缓解或解决这个问题?
3. 如果需要在不停服的情况下临时提升 Metaspace 上限,应该使用什么 JVM 参数?
> **答题框架提示**:
> - 第 1 问先解释 Metaspace 的存储位置和底层实现机制,再对比永久代
> - 第 2 问分别从"减少 Class 数量"、"调整参数"、"架构层面"三个维度思考
> - 第 3 问直接给出参数名即可
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | D | 程序计数器记录当前线程执行的字节码行号,只需极小的空间,是唯一不会发生 OOM 的区域。堆和方法区可能 OOM,栈会抛 StackOverflowError。 |
| Q2 | B | 元空间使用本地内存(Direct Memory),而非堆内内存,上限通过 `-XX:MaxMetaspaceSize` 控制。A 错在说"堆内",C 错在说"不受限",D 错在错误类型不同(元空间 OOM 类型为 `OutOfMemoryError: Metaspace`)。 |
| Q3 | A | 正确的创建顺序是:②类加载检查 → ⑤分配内存 → ③零值初始化 → ①设置对象头 → ④执行 init 方法。必须先定位类才能分配对应的内存空间。 |
| Q4 | B | 标记-复制将存活对象复制到另一半并清空原区,天然紧凑无碎片但浪费一半空间;标记-整理是移动存活对象到一端后清理边界外内存。A 恰好说反了。 |
| Q5 | C | 关闭检查只是掩耳盗铃,不能解决根本问题。A(dump 分析)、B(加大堆或修泄漏)、D(识别僵尸对象)都是合理的排查方向。 |
| Q6 | B | 此错误说明 OS 级别的线程数上限触顶,每个 Java 线程映射为一个原生线程,消耗约 1MB 栈内存。可通过减少并发线程数或提升系统 ulimit 来解决。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | (1)对象 (2)实例链 | Dominator Tree 按支配关系展示对象图,最大的"支配者"对象排在最上方,帮助快速定位持有大量引用的可疑对象。 |
| F2 | (1)Java 9+ | `finalize()` 方法从 Java 9 开始被标记为 deprecated,最终在 Java 18 被正式移除。JVM 会在第二次标记时尝试调用它。 |
| F3 | (1)初始堆大小 (2)最大堆大小 | `-Xms` 和 `-Xmx` 分别控制堆的初始值和最大值。设成同一值可以避免运行时因动态扩容/缩容带来的性能损耗。 |
### 简答题参考答案
S1:**参考答案要点**:
1. Metaspace 使用本地内存而非堆内内存,理论上只受本机物理内存总量限制;永久代则在堆内实现,大小固定容易撑爆。动态代理和热部署会产生大量 Class 定义,耗尽 Metaspace。
2. ① 减少 Class 数量:缩小 MyBatis 扫描包路径范围;减少不必要的 CGLIB 代理;评估热部署框架的生产必要性。② 调整参数:适当增大 `-XX:MaxMetaspaceSize`。③ 架构优化:避免过于频繁的热部署重启,或考虑更优雅的热更新方案。
3. `-XX:MaxMetaspaceSize=512m`(或其他合理值)。注意这是一个启动参数,不停服修改需要配合 JMX 或重启。
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。
## 关联笔记
- [[01.Java/jvm/垃圾回收算法与收集器]]
@@ -0,0 +1,138 @@
---
tags: [test/review, java, gc-algorithm, cms, g1, zgc, young-gen]
create time: 2026-08-09 12:00
---
# 垃圾回收算法与收集器_测试题
## 概述
本测试覆盖标记-清除/复制/整理三种经典 GC 算法、分代理论依据,以及 CMS、G1、ZGC 三大收集器的设计原理和选型策略,共 10 道题目(6 道选择题 + 3 道填空题 + 1 道简答题)。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
新生代 Eden:S0:S1 的经典比例设计中,Eden 区占新生代的份额大约是:
A. 1/2(50%)
B. 1/3(约 33%)
C. 4/5(80%)
D. 9/10(90%)
### Q2(基础)— 考察概念记忆
以下哪种 GC 算法的主要缺点是**会产生大量内存碎片**?
A. 标记-复制(Mark-Copy)
B. 标记-整理(Mark-Compact)
C. 标记-清除(Mark-Sweep)
D. G1 的 Mixed GC
### Q3(进阶)— 考察核心原理
CMS 收集器有四个阶段,其中**不会暂停用户线程**的阶段是:
A. 初始标记(Initial Mark)
B. 并发标记(Concurrent Mark)
C. 预标记(Re-Mark)
D. 以上三个阶段都会 STW
### Q4(进阶)— 考察比较与辨析
G1 收集器与传统的 CMS 收集器相比,最核心的架构区别在于:
A. G1 不再物理分隔新生代和老年代,而是将堆划分为多个大小相等的 Region
B. G1 只使用标记-清除算法,而 CMS 使用标记-复制算法
C. G1 的所有 GC 动作都在单线程内串行完成
D. G1 不支持并发回收,必须依赖 Full GC
### Q5(深入)— 考察场景推理
某服务堆大小约为 16GB,要求 GC 暂停时间控制在 200ms 以内,对吞吐量有一定要求但不追求极致低延迟。根据收集器选型决策树,最合适的选择是:
A. CMS
B. G1
C. ZGC
D. Parallel Old
### Q6(深入)— 考察边界场景
ZGC 能够实现暂停时间不超过 10ms(无论堆大小)的核心技术是:
A. 在写屏障中记录所有引用修改,然后批量重定位
B. 染色指针 + 加载屏障,将原本沉重的写屏障移到加载时机
C. 利用多核并行地在一个 STW 窗口内完成全量回收
D. 将所有对象分配到连续内存区域,避免引用失效
---
## 二、填空题(3道)
### F1 — 填空1
CMS 收集器有三个显著缺陷:**浮动垃圾**(并发清扫阶段产生的新对象只能等下一次 GC 处理)、__(1)__(基于标记-清除算法容易产生碎片)、以及 CPU 资源敏感(并发阶段和用户代码共享 CPU)。CMS 在 JDK __(2)__ 中被彻底移除。
> **提示**: 回顾 CMS 缺陷部分的具体描述和生命周期。
### F2 — 填空2
G1 收集器中有一个特殊的概念叫 ROI(Return of Investment),用于排序 Region 的回收优先级——优先回收__(1)__最大的 Region。此外,超过半个 Region 大小的超大对象会直接分配到__(2)__ Region,以避免碎片问题。
> **提示**: ROI = 回收得到的空间 / 花费的时间,超大对象的特殊命名与其用途相关。
### F3 — 填空3
根据选型决策树:堆 < 4GB 推荐 Parallel GC(吞吐优先)或 CMS(JDK 8 低延迟需求);4GB ≤ 堆 ≤ 32GB 推荐 __(1)__;堆 > 32GB 且要求暂停 < 10ms 推荐 __(2)__。
> **提示**: 这是生产环境中最常用的两个收集器选择分界线。
---
## 三、简答题(1道)
### S1
某电商大促活动即将开始,负责团队正在做线上 GC 压测。现有三个待选收集器:CMS、G1、ZGC。已知以下条件:
- 堆大小约 24GB
- 要求 GC 暂停时间尽量短(P99 < 300ms)
- 业务允许一定的吞吐量损失
- 当前运行在 JDK 11 之上
请回答:
1. 你会推荐哪个(些)收集器?给出决策理由。
2. ZGC 相比 G1 的核心优势是什么?它的两项关键技术名词是什么?
3. 如果在压测中发现使用 CMS 时频繁出现 Full GC,可能的原因是什么?(至少给出两条)
> **答题框架提示**:
> - 第 1 问先看堆大小落在选型树的哪个区间
> - 第 2 问直接提取 ZGC 的技术细节
> - 第 3 问从 CMS 的三个缺陷出发推导
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | C | HotSpot 通过 Eden:S0:S1 = 8:1:1 的比例设计,Eden 占 8/10 = 80%,让绝大多数新对象直接分配到 Eden。只有少量长寿对象才进入 Survivor。 |
| Q2 | C | 标记-清除算法不移动对象、不复制,只是在清除未标记区域时留下空洞,产生碎片。标记-复制和标记-整理都能避免碎片。 |
| Q3 | B | CMS 的初始标记和预标记都需要短暂 STW,并发标记和并发清扫是完全与用户线程并行的。所以选 B。 |
| Q4 | A | G1 的最大创新是将堆划分为最多 2048 个 Region(每个 1~32MB),不再物理分隔新生代和老年代。B 错误(G1 不使用纯标记-清除),C 错误(G1 并行并发),D 错误(G1 支持并发)。 |
| Q5 | B | 堆大小 24GB 落在 4GB~32GB 区间,选型决策树明确指出这个范围推荐 G1。ZGC 虽然也适用但成本更高(吞吐量略低),Parallel Old 吞吐优先不适合低延迟场景。 |
| Q6 | B | ZGC 的核心创新是染色指针(在指针高位 bit 编码颜色信息)和加载屏障(读引用时才执行重定位),将写屏障移到加载时机。A 描述的是传统写屏障方式。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | (1)内存碎片 (2)JDK 14 | CMS 基于标记-清除算法,必然产生碎片,可能导致大对象无法分配而提前触发 Full GC。CMS 在 JDK 9 标记废弃,JDK 14 彻底移除。 |
| F2 | (1)ROI(回收价值) (2)Humongous | G1 按 ROI 排序优先回收性价比最高的 Region。超大对象(超过半 Region)直接放入 Humongous Region 避免跨区碎片。 |
| F3 | (1)G1 (2)ZGC | 4GB~32GB 是 G1 的主战场(大多数生产环境的默认选择),>32GB 且要求低延迟时切换 ZGC。 |
### 简答题参考答案
S1:**参考答案要点**:
1. 堆大小 24GB 落在 4GB~32GB 区间,推荐 G1 作为首选。如果预算允许且对延迟要求更严苛,也可以评估 ZGC。JDK 11 以上已支持 ZGC 生产使用。
2. ZGC 的核心优势是暂停时间绝对不超过 10ms,与堆大小无关。两项关键技术:染色指针(Colored Pointers)和加载屏障(Load Barrier)。
3. CMS 频繁 Full GC 的可能原因:① 内存碎片过多导致大对象无法在 Eden 分配直接进入老年代,老年代很快满;② CMS 并发清扫阶段产生的"浮动垃圾"积累到下一次的 Full GC;③ 老年代阈值配置不合理,GC 未能及时触发。
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。
## 关联笔记
- [[01.Java/jvm/JVM 内存模型]]