8.1 KiB
tags, create time
| tags | 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 内存模型的知识,回答以下问题:
- 为什么 Metaspace 会耗尽?它与 JDK 7 的永久代有什么区别?
- 可以从哪些维度(至少 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:参考答案要点:
- Metaspace 使用本地内存而非堆内内存,理论上只受本机物理内存总量限制;永久代则在堆内实现,大小固定容易撑爆。动态代理和热部署会产生大量 Class 定义,耗尽 Metaspace。
- ① 减少 Class 数量:缩小 MyBatis 扫描包路径范围;减少不必要的 CGLIB 代理;评估热部署框架的生产必要性。② 调整参数:适当增大
-XX:MaxMetaspaceSize。③ 架构优化:避免过于频繁的热部署重启,或考虑更优雅的热更新方案。 -XX:MaxMetaspaceSize=512m(或其他合理值)。注意这是一个启动参数,不停服修改需要配合 JMX 或重启。
评分标准:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点。