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

8.1 KiB
Raw Blame History

tags, create time
tags create time
test/review
java
jvm-memory
heap
metaspace
oom
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 个要点即可得满分;完全正确需覆盖全部要点。

关联笔记