Files
autumn-recruitment/01.Java/jvm/JVM 内存模型_test.md
T

144 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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/垃圾回收算法与收集器]]