Skip to content

Java-JVM内存与GC

大类:高级Java · 共 35 题 · 检索页定位

选择题(17)

q031 · 简单

下列 JVM 运行时数据区中,属于线程共享的区域是?
A. 虚拟机栈
B. 程序计数器
C. Java 堆
D. 本地方法栈

参考答案要点
  • 堆和方法区(元空间)是线程共享的;虚拟机栈、本地方法栈、程序计数器是线程私有的
  • 堆是 JVM 中最大的一块内存区域,几乎所有的对象实例都在堆上分配,也是垃圾收集器管理的主要区域
  • 程序计数器是当前线程执行字节码的行号指示器;虚拟机栈存储栈帧(局部变量表、操作数栈、动态链接、方法出口)
  • JDK 8 起方法区由元空间(Metaspace)实现,使用本地内存,不再受 JVM 堆大小限制
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q034 · 中等

CMS 垃圾收集器采用的垃圾回收算法是?
A. 标记-清除算法
B. 标记-复制算法
C. 标记-整理算法
D. 分代收集算法

参考答案要点
  • CMS(Concurrent Mark Sweep)以获取最短停顿时间为目标,采用标记-清除算法
  • CMS 四个阶段:初始标记(STW)、并发标记、重新标记(STW)、并发清除
  • 标记-清除的缺点:产生内存碎片,碎片过多导致大对象无法分配时触发 Full GC
  • 标记-复制适用于新生代(存活对象少),标记-整理适用于老年代(存活对象多、无碎片但移动对象成本高)
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q191 · 简单

JVM 判定一个对象可以被回收,采用的主要机制是?
A. 引用计数法:对象的引用计数归零即可回收
B. 可达性分析:从 GC Roots 出发沿引用链搜索,不可达的对象可回收
C. 老年代定期全量扫描,存活超过一定时长的对象自动回收
D. 对象的 finalize() 方法执行后立即回收

参考答案要点
  • 可达性分析(GC Roots Tracing):从 Roots 沿引用链搜索,不可达即可回收——B 正确
  • A 错在:引用计数无法处理循环引用(两个对象互相引用,计数永不归零),Java 因此弃用——昨晚学员恰在此点答反
  • C 无此机制;D:finalize() 在 JDK 9 起废弃,且不决定回收时机
  • GC Roots 四类:栈帧局部变量/方法参数、类静态变量、常量池引用、JNI 本地方法引用
来源:generated

q193 · 困难

并发标记阶段存在三色标记漏标问题(并发标记线程与用户线程同时运行导致存活对象被误标为白色)。CMS 和 G1 分别采用哪种方案解决?
A. CMS 采用原始快照(SATB),G1 采用增量更新
B. CMS 采用增量更新,G1 采用原始快照(SATB)
C. 两者都采用增量更新
D. 两者都采用原始快照(SATB)

参考答案要点
  • 能说清三色标记漏标的两个必要条件:插入黑->白引用、且该白对象与灰对象间的引用被删除
  • 增量更新:关注新增引用,黑色对象到白色对象的新边重新把黑变灰,CMS 采用
  • 原始快照(SATB):关注删除引用,删除时记录旧引用快照,按初始对象图标记,G1 采用
  • 能对比两种方案的写屏障拦截点与浮动垃圾差异
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q196 · 困难

关于 ZGC 和 Shenandoah 两个低延迟收集器,下列说法错误的是?
A. 两者的共同目标都是把整理(压缩)阶段并发化,把 STW 控制在毫秒以内
B. ZGC 主要靠着色指针+读屏障实现并发整理,Shenandoah 主要靠转发指针/读引用屏障
C. ZGC 和 Shenandoah 在最初发布时都必须以分代模式运行,否则无法工作
D. 两者的停顿时间基本都不随堆大小增长而明显增加

参考答案要点
  • 知道两者都是并发整理的低延迟收集器,目标是亚毫秒/毫秒级停顿
  • ZGC 用着色指针+读屏障;Shenandoah 用转发指针(Brooks Pointer)及读引用屏障
  • 两者最初都不分代(分代 ZGC 在 JDK 21 才转正,Shenandoah 至今未分代),C 表述错误
  • 能补充对比:CMS/G1 分代设计 vs ZGC/Shenandoah 全域并发设计的取舍
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q199 · 中等

类加载过程的"准备"阶段,关于 static 变量的内存分配,下列说法正确的是?
A. static int a = 1 在准备阶段 a 就被赋值为 1
B. static int a = 1 在准备阶段 a 为默认值 0,要到初始化(<clinit>)阶段才赋值为 1
C. static final int 常量在准备阶段也不会被赋值
D. 实例变量在准备阶段就会随类一起分配内存并赋默认值

参考答案要点
  • 准备阶段为类静态变量分配内存并赋零值,a=1 要等初始化阶段执行 <clinit>
  • 例外:static final 编译期常量(ConstantValue 属性)在准备阶段直接赋值
  • 实例变量随对象在堆上分配,与类准备阶段无关
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q201 · 困难

关于 Tomcat 的类加载机制,下列描述正确的是?
A. Tomcat 严格遵循双亲委派,Web 应用的类全部委托给 Common 类加载器加载
B. WebAppClassLoader 对 java.* 等核心类库仍会委派给上级加载,但对 /WEB-INF/classes、/WEB-INF/lib 下的应用类优先自己加载,不同 Web 应用之间类相互隔离
C. 两个不同 Web 应用引入同一个类,会由同一个类加载器加载,可直接互相访问
D. Tomcat 中每个 JSP 文件都会生成一个独立的 WebAppClassLoader

参考答案要点
  • Tomcat 自定义多层类加载器:Common/Catalina/Shared + 每个 Web 应用一个 WebAppClassLoader
  • WebAppClassLoader 对应用类先本地加载(打破双亲委派),但核心类(java.* 等)仍委派上级保证安全
  • 不同应用的类加载器相互隔离,同名类不冲突,实现应用隔离
  • JSP 是每个文件一个 JasperLoader(而非 WebAppClassLoader)负责热更新
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q204 · 困难

G1 收集器下,把 -XX:MaxGCPauseMillis 从 200ms 调整为 20ms,最可能出现的现象是?
A. 单次 GC 停顿变短,但每次回收的 Region 数量减少,GC 频率上升,总吞吐量下降
B. Region 大小自动翻倍,单次回收量更大
C. 老年代从此不再参与 Mixed GC
D. 直接触发 Full GC 以满足停顿目标

参考答案要点
  • 理解 MaxGCPauseMillis 是软目标,G1 通过暂停预测模型调整年轻代大小和 CSet 数量
  • 停顿目标过小->每次回收的 Region 变少->回收频率升高、吞吐下降
  • 知道 G1 调优一般先固定停顿目标再观察,而不是盲目调小
  • 能提及 Region Size 通常由堆大小自动算出,不随该参数翻倍
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q513 · 中等

JVM 可达性分析算法从 GC Roots 出发搜索引用链。下列哪项通常【不属于】GC Roots?
A. 虚拟机栈栈帧局部变量表中引用的对象
B. 方法区中类静态属性引用的对象
C. 方法区中常量(如字符串常量)引用的对象
D. 某个普通 Java 对象的实例字段所引用的对象

参考答案要点
  • 正确答案 D. 某个普通 Java 对象的实例字段所引用的对象——实例字段只是引用链上的普通中间节点,可达性分析的起点必须是 GC Roots,自身不可达的对象所引用的东西同样不可达
  • A 错:虚拟机栈(栈帧局部变量表)中引用的对象是典型的 GC Roots
  • B 错:方法区中类静态属性引用的对象属于 GC Roots
  • C 错:方法区中常量引用的对象(如字符串常量池引用)同样属于 GC Roots;此外本地方法栈中 JNI 引用的对象也是 GC Roots
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)

q514 · 简单

HotSpot 判定对象是否存活时没有采用引用计数算法,主要原因是?
A. 引用计数的加减开销太大,JVM 完全无法承受
B. 引用计数必须暂停所有用户线程才能统计准确
C. 引用计数算法只适用于新生代,不适用于老年代
D. 引用计数无法处理循环引用:互相引用但整体已不可达的两个对象计数不为 0,永远不会被回收

参考答案要点
  • 正确答案 D. 引用计数无法处理循环引用……——这是 JVM 选择可达性分析的根本原因
  • A 错:引用计数开销并非不可接受,.NET/Python 等都有使用,不是主因
  • B 错:引用计数可以实时增量维护,不需要 STW
  • C 错:引用计数与分代无关,老年代同样可以计数
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)

q515 · 中等

关于 Java 的四种引用类型(强度从高到低:强、软、弱、虚),下列说法正确的是?
A. 软引用关联的对象在任何一次 GC 中都会被回收
B. 弱引用关联的对象只要还存在弱引用指向,GC 也永远不会回收它
C. 虚引用(PhantomReference)无法通过 get() 直接拿到对象,常用于对象被回收时收到系统通知,典型场景是配合 Cleaner 管理 NIO DirectByteBuffer 的堆外内存
D. 强引用关联的对象即使抛出 OutOfMemoryError 也不会被回收,因此没有任何办法让它被回收

参考答案要点
  • 正确答案 C. 虚引用无法直接获取对象,用于对象回收时的通知/清理钩子,DirectByteBuffer 堆外内存回收就是靠它
  • A 错:软引用只在内存不足时才被回收,正适合做内存敏感的缓存
  • B 错:弱引用只要发生 GC 就会被回收,ThreadLocalMap 的 key 就是典型弱引用
  • D 错:前半句对(宁可 OOM 也不回收强引用),但把引用显式置 null 断开后对象就可以被回收,并非没有办法
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-interview-questions.html)

q516 · 简单

Oracle JDK 8(Server 模式,未显式指定收集器)默认使用的垃圾收集器组合是?
A. Serial + Serial Old
B. ParNew + CMS
C. Parallel Scavenge + Parallel Old
D. G1

参考答案要点
  • 正确答案 C. Parallel Scavenge + Parallel Old——JDK 8 默认组合,吞吐量优先
  • A 错:Serial 组合是 Client 模式/单核小内存场景的选择
  • B 错:CMS 需要显式开启,JDK 9 起被标记废弃、JDK 14 正式移除
  • D 错:G1 是 JDK 9 及之后的默认收集器
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)

q517 · 困难

运行 CMS 收集器的服务频繁出现 Concurrent Mode Failure 并退化为长时间的 Full GC。关于该现象,下列说法正确的是?
A. CMS 并发清理阶段用户线程仍在分配对象,若老年代预留空间不足以容纳浮动垃圾与新对象,就会触发 Concurrent Mode Failure,退化为 Serial Old 的单线程 STW Full GC
B. Concurrent Mode Failure 的根因是并发标记阶段发生了三色标记漏标
C. 把 CMSInitiatingOccupancyFraction 调得更高(如 92%)可以让 CMS 更早启动回收,有效减少该问题
D. CMS 采用标记-整理算法,不存在内存碎片,不可能因碎片导致空间不足

参考答案要点
  • 正确答案 A. 并发回收期间老年代预留不足(浮动垃圾+并发分配)导致 CMF,退化为 Serial Old 全停顿整理
  • B 错:三色标记漏标对应的是存活对象被误回收问题,与 CMF 无关(CMS 用增量更新解决)
  • C 错:该百分比是触发 CMS 的老年代占用阈值,调高意味着更晚启动、预留更少,反而更容易 CMF,应适当调低
  • D 错:CMS 是标记-清除算法,内存碎片正是它的固有缺点之一,碎片过多也会提前触发 Full GC
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)

q518 · 中等

关于 JVM 的安全点(Safepoint),下列说法正确的是?
A. 发起 STW 时,正在执行的线程会被立即强制中断停下,无需到达特定位置
B. 采用主动式中断:线程只有运行到最近的安全点(典型位置如方法调用、循环回跳、异常跳转处)才会主动挂起,等待 GC
C. 安全点设置得越密集,JVM 整体运行性能一定越好
D. 处于睡眠或阻塞状态的线程也必须先跑到安全点才能被 GC 挂起

参考答案要点
  • 正确答案 B. 主动式中断,线程跑到最近的安全点主动挂起——安全点太少会让线程长时间到不了,太多又会拖慢正常执行
  • A 错:不是任意位置立即中断,是各线程自行轮询中断标志并在安全点主动挂起
  • C 错:安全点本身有维护成本,密度过大会影响程序执行效率,并非越多越好
  • D 错:睡眠/阻塞线程已经离开运行状态,按安全区域(region)直接处理,不需要跑到安全点
来源:synthesized

q519 · 中等

关于垃圾收集器的选型,下列场景与收集器的匹配最恰当的是?
A. 6~8 GB 堆的在线服务,要求 GC 停顿可控在百毫秒级、兼顾吞吐,不希望精细调分代参数——选 G1
B. 几十 MB 小堆的客户端小工具程序——选 ZGC
C. 超过 32 GB 的大堆,要求停顿低于 10ms——选 CMS
D. 离线批处理任务,只关心总吞吐量、完全不在乎单次停顿——选 Serial

参考答案要点
  • 正确答案 A. 中大堆、停顿可控、吞吐与延迟平衡正是 G1 的定位(可预测停顿模型)
  • B 错:小堆客户端程序用 Serial 即可,ZGC 面向超大堆超低延迟场景
  • C 错:CMS 无法胜任超大堆且 JDK 14 已移除,该场景应选 ZGC
  • D 错:吞吐量优先应选 Parallel Scavenge + Parallel Old,Serial 只适合小内存单核
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-interview-questions.html)

q520 · 中等

关于 NIO 中 DirectByteBuffer 分配的堆外直接内存,下列说法正确的是?
A. 它位于 JVM 堆内,直接受 -Xmx 约束
B. 由垃圾收集器在 Young GC 时直接扫描堆外内存并回收
C. 在 JDK 8 中它存放在永久代
D. 它不计入 -Xmx 管辖的堆空间,上限由 -XX:MaxDirectMemorySize 控制(未设置时默认约等于 -Xmx),分配不足会抛出 OutOfMemoryError: Direct buffer memory

参考答案要点
  • 正确答案 D. 堆外内存在 -Xmx 之外,由 -XX:MaxDirectMemorySize 单独限制,耗尽抛 Direct buffer memory OOM
  • A 错:直接内存的关键词就是堆外,不受堆大小直接约束
  • B 错:GC 不直接管理堆外内存,DirectByteBuffer 依靠 Cleaner(基于虚引用)在其堆内对象被回收时释放对应堆外内存,堆外内存的释放是间接的、有延迟的
  • C 错:JDK 8 已移除永久代(改为元空间),且元空间存的也不是直接内存
来源:synthesized

q521 · 简单

HotSpot 新生代(Eden + 两个 Survivor)采用标记-复制算法回收,主要原因是?
A. 标记-复制空间利用率最高,完全不会浪费内存
B. 新生代对象大多朝生夕死、存活率低,只需复制少量存活对象,开销小且天然没有内存碎片
C. 标记-复制算法可以完全避免 Stop-The-World
D. 老年代与新生代一样,都最适合标记-复制

参考答案要点
  • 正确答案 B. 存活对象少,复制的代价远低于标记-清除的碎片处理,且复制后天然紧凑无碎片
  • A 错:复制算法需要预留空间(Eden:S0:S1 约 8:1:1),存在空间浪费
  • C 错:复制过程同样需要 STW,只是因为只处理少量存活对象而耗时短
  • D 错:老年代对象存活率高,复制开销大,更适合标记-清除/标记-整理
来源:[javaguide.cn](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)

简答题(15)

q016 · 简单

说说 JVM 运行时数据区的划分,哪些区域可能发生 OOM?每个区域存放什么?

参考答案要点
  • 线程私有:程序计数器、虚拟机栈(栈帧/局部变量表)、本地方法栈;栈溢出StackOverflow与OOM
  • 线程共享:堆(对象实例,GC主战场,OOM最多发地)、方法区/元空间(类元信息,动态生成类泄漏会OOM)
  • 直接内存:NIO Buffer,不算运行时数据区但会OOM
  • 字符串常量池位置演进(JDK7入堆)属加分
  • 直接内存溢出的排查特征(Heap dump看不出)是高级信号
来源:seed

q017 · 中等

G1 和 CMS 的核心区别是什么?为什么 JDK 9 之后 G1 成了默认收集器?什么场景下你会考虑换 ZGC 或 Shenandoah?

参考答案要点
  • CMS:标记-清除,并发标记并发清理,主打低停顿但内存碎片+并发失败风险
  • G1:标记-整理(Region间复制),可预测停顿模型(-XX:MaxGCPauseMillis),堆>6GB优势明显
  • G1的Region化堆+记忆集(RSet)+混合回收(Mixed GC)机制
  • 换ZGC/Shenandoah场景:超低延迟要求(P99<10ms)、超大堆(TB级)
  • 能讲「停顿时间vs吞吐量」的trade-off框架而非背特性
来源:seed

q018 · 中等

线上服务每隔几分钟一次 Full GC,响应时间抖动明显。说说你完整的排查路径:从发现到定位到解决。

参考答案要点
  • 先看GC日志确认频率与耗时,区分Promotion Failure/元空间/显式System.gc
  • 监控对比:堆使用曲线、对象增长速率,判断内存泄漏还是容量不足
  • jmap -histo / heap dump + MAT分析支配树,找大头对象及其引用链
  • 常见元凶:本地缓存无界、大查询一次加载全表、ThreadLocal未清理、动态类生成
  • 解决分层:代码修泄漏/调缓存上限/调参(堆大小、G1混合回收阈值)只是兜底
  • 排查全程有工具链和证据链意识,不是背命令
来源:seed

q032 · 中等

对象在什么情况下会进入老年代?

参考答案要点
  • 长期存活的对象:对象年龄计数器每经历一次 Minor GC 加 1,超过阈值(默认 15,由 -XX:MaxTenuringThreshold 控制)晋升老年代
  • 大对象直接进入老年代:由 -XX:PretenureSizeThreshold 控制;G1 中对象超过 Region 容量 50% 会分配到 Humongous 区域
  • 动态年龄判定:Survivor 中同龄对象总大小超过 Survivor 空间的一半,年龄大于等于该年龄的对象直接晋升
  • Minor GC 后存活对象放不下 Survivor、空间分配担保失败时,对象直接进入老年代
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q033 · 困难

有了 CMS 为什么还要引入 G1?请说明两者的核心区别。

参考答案要点
  • CMS 采用标记-清除算法,容易产生内存碎片,可能触发 Full GC;G1 整体基于标记-整理,Region 之间是复制,基本不产生碎片
  • G1 把堆划分为多个大小相等的 Region,每个 Region 可动态扮演新生代/老年代角色,并有专门的 Humongous 区存大对象
  • G1 可预测停顿:-XX:MaxGCPauseMillis 可设置期望停顿目标,G1 优先回收垃圾最多的 Region(收集收益最高的区域)
  • CMS 的 Concurrent Mode Failure(浮动垃圾、老年代空间不足)会导致退化为 Serial Old 单线程整理;G1 在大内存多核场景下表现更稳定
  • 版本演进:CMS 在 JDK 9 被标记弃用、JDK 14 被移除,G1 从 JDK 9 起成为默认垃圾收集器
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q189 · 中等

从「一个对象怎么才算没用了」讲起,回答三问:
(1) new 出来的对象本体、和方法里持有它的局部变量(引用),分别存在 JVM 的哪个区域?
(2) GC 判断一个对象可以被回收的标准机制是什么?哪些东西可以作为 GC Roots(至少举 4 类)?
(3) 除了堆之外,还有哪两块内存区域是 OOM 高发区?各自典型的一次泄漏场景是什么?

参考答案要点
  • 对象实例在堆;局部变量引用在虚拟机栈栈帧的局部变量表——「引用在栈、对象在堆」必须分清(早晨答混的点)
  • 可达性分析:从 GC Roots 沿引用链搜索,不可达即可回收;不是引用计数(讲出为何弃用引用计数=循环引用是加分)
  • GC Roots ≥4 类:栈帧局部变量/方法参数、类静态变量、常量池引用(如字符串常量)、JNI 本地方法引用
  • OOM 高发区:方法区/元空间(CGLIB/反射动态生成类、热部署泄漏)、直接内存(NIO Buffer;heap dump 看不到是排查特征)
  • 主动延伸:分代收集里新生代/老年代的判定与回收时机、四种引用强度,是加分
来源:generated

q192 · 中等

昨晚你把「引用计数」当成了 JVM 的回收判定机制,今晚正面回炉,三问:
(1) 引用计数法为什么被 JVM 弃用?举一个它无法处理的具体场景。
(2) JVM 实际采用什么机制判定对象可回收?请描述这个过程。
(3) 哪些引用可以作为 GC Roots?至少列出 4 类。

参考答案要点
  • (1) 循环引用:两个对象互相引用时,各自的引用计数永不归零,即使整体已不可达也无法回收
  • (2) 可达性分析:从 GC Roots 出发沿引用链向下搜索,能到达的对象存活,搜索不到的判定为可回收
  • (3) 四类 Roots:栈帧局部变量/方法参数、类静态变量、常量池引用(如字符串常量)、JNI 本地方法引用
  • 加分:分代收集(新生代 Minor GC / 老年代 Major GC)、四种引用强度(强/软/弱/虚)
来源:generated

q194 · 困难

深挖 G1 收集器:Region、RSet(记忆集)、CSet(回收集)各自的作用是什么?大对象在 G1 中如何分配?Mixed GC 的回收对象如何挑选?

参考答案要点
  • 堆被划分为等大的 Region(1MB~32MB,2 的幂),每个 Region 动态充当 Eden/Survivor/Old/Humongous
  • RSet 记录"谁引用了我"(点入精度),避免跨 Region 引用扫描时整堆遍历,是 G1 可停顿可控的关键
  • CSet 是本次 GC 要回收的 Region 集合,Mixed GC 依据并发标记结果按回收收益(垃圾占比)排序挑选
  • 大对象(>Region 一半)直接分配到连续 Humongous Region,超过整堆一半直接失败,可通过 G1HeapRegionSize 调大 Region 缓解
  • 能提到写屏障维护 RSet、MaxGCPauseMillis 驱动暂停预测模型
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q195 · 困难

ZGC 为什么能做到停顿时间不超过 1ms 且不随堆大小增长?请从着色指针、读屏障、并发整理三个角度说明其核心机制。

参考答案要点
  • 着色指针:在 64 位指针中借位标记 Marked0/Marked1/Remapped 等颜色,把标记信息直接放在指针上
  • 读屏障:在对象引用加载时检查指针颜色并"自愈"(访问到旧地址时原地修正),保证并发移动对象期间用户线程也能正确访问
  • 标记、转移(整理)几乎全程与用户线程并发执行,STW 只剩初始标记/再标记等极短阶段
  • 停顿时间与堆大小无关,可支持 TB 级大堆;能提到 JDK 21 分代 ZGC(JEP 439)为加分项
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q197 · 困难

什么是逃逸分析?它能为 JVM 带来哪些优化?对象是否一定分配在堆上?

参考答案要点
  • 逃逸分析是分析对象作用域的分析技术:全局逃逸/方法逃逸/不逃逸
  • 三种优化:标量替换(不逃逸对象拆成基本类型标量,不分配完整对象)、栈上分配(理论概念,HotSpot 主要靠标量替换实现同等效果)、锁消除/同步消除
  • 明确回答:不一定,未逃逸且被标量替换的对象不占用堆内存;-XX:+DoEscapeAnalysis 默认开启
  • 能提到逃逸分析在 C2 编译的 JIT 代码上进行的加分项
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q198 · 困难

讲讲 JVM 的 JIT 即时编译:HotSpot 有哪两类编译器,各自特点?热点代码如何探测?什么是分层编译?

参考答案要点
  • C1(客户端编译器):编译快、优化浅,适合启动速度;C2(服务端编译器):编译慢、优化深(如逃逸分析、内联、去虚拟化)
  • 热点探测:基于方法调用计数器与回边计数器(触发 OSR 栈上替换),阈值与 CompileThreshold 相关
  • 分层编译(Tiered Compilation,默认开启)将编译分为 5 层,先 C1 快速升温再由 C2 深度优化
  • 能提到解释执行 vs 编译执行、CodeCache 以及方法退化/去优化是加分项
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q200 · 困难

什么是双亲委派模型?为什么需要它?哪些场景必须打破双亲委派?请举出至少两个典型例子并说明其打破方式。

参考答案要点
  • 定义:类加载器收到请求先委派给父加载器,只有父加载器无法完成才自己加载(Bootstrap->Platform->Application)
  • 意义:保证类的一致性与安全(防止核心类库被篡改,如自定义 java.lang.String 加载失败),避免重复加载
  • 典型打破场景一:SPI/JDBC——核心接口在 Bootstrap 加载,实现类由线程上下文类加载器(Thread Context ClassLoader)加载,反向委派
  • 典型打破场景二:Tomcat——WebAppClassLoader 对应用类先自己加载(违反先委派父级),实现应用隔离;其他:热部署(OSGi/自定义类加载器替换)、JRebel
  • 能说明自定义类加载器重写 loadClass vs findClass 的区别是加分项
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q202 · 中等

JMM(Java 内存模型)中有哪些 happens-before 规则?请至少列举五条,并说明它与 volatile 的关系。

参考答案要点
  • 程序顺序规则:同一线程内按代码顺序前面的操作 happens-before 后面的操作
  • 监视器锁规则:对同一锁的 unlock happens-before 后续的 lock
  • volatile 规则:对 volatile 变量的写 happens-before 后续对该变量的读
  • 线程启动/终止规则:Thread.start() 前的操作 happens-before 线程内操作;线程内操作 happens-before join() 返回
  • 传递性规则;能说明 volatile 语义(可见性+禁止重排)正是通过建立 happens-before 边实现
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/javathread.html)

q205 · 中等

JDK 8 为什么用元空间(Metaspace)替代永久代(PermGen)?两者在存储位置和限制方式上有何不同?字符串常量池在 JDK 7 之后放在哪里?

参考答案要点
  • 永久代在堆内,受 -XX:MaxPermSize 硬限制,类加载多的应用易 OOM: PermGen space,且与堆 GC 耦合
  • 元空间使用本地内存(native memory),默认只受物理内存限制,可用 -XX:MaxMetaspaceSize 设上限
  • 元空间存放类元数据;字符串常量池 JDK 7 起从永久代移到堆中,便于 GC 回收
  • 能提方法区只是规范概念,永久代/元空间是其实现,属加分项
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q948 · 中等

不看任何材料,白纸默写:(1) JVM 哪四类引用可以作为 GC Roots?每类各举一个具体例子。(2) 用一句话说清 JVM 为什么弃用引用计数法。

参考答案要点
  • 四类:虚拟机栈栈帧局部变量/方法参数、方法区类静态变量、常量池引用(如字符串常量)、本地方法栈 JNI 引用,每类各 2 分
  • 举例子要求具体(如「方法里 new 的对象」「static Map 缓存」),只背名词不给例子扣一半
  • 弃用原因一句话:循环引用的两个对象计数永不归零,整体已不可达却无法回收
  • 评分口径:能白纸默写出 ≥3 类即及格线,四类全对+例子具体=10 分
来源:generated

场景题(3)

q035 · 困难

线上服务突然频繁 Full GC,接口响应变慢,你如何排查和处理?

参考答案要点
  • 先用 jstat -gcutil <pid> 1000 观察 GC 频率与各代占用,确认 Full GC 次数是否持续增长、老年代是否居高不下
  • jmap -histo <pid> 查看堆内对象排行,jmap -dump:format=b,file=heap.hprof 导出堆快照,用 MAT/VisualVM 分析大对象与引用链定位泄漏源头
  • 常见原因:内存泄漏(静态集合、ThreadLocal 未 remove、连接未关闭)、老年代空间偏小、大对象直接进老年代、System.gc() 被显式调用、元空间不足
  • 对症处理:增大老年代或调整 -Xms/-Xmx、拆分大对象、修复泄漏代码、关闭显式 System.gc()(-XX:+DisableExplicitGC)、更换收集器或调优参数
  • 处理后在压测环境验证,并持续观察 GC 日志(-Xloggc + PrintGCDetails)确认效果
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q203 · 困难

线上一个 Java 服务老年代内存持续缓慢上涨,几次 Full GC 后老年代占用也不回落,最终抛出 OutOfMemoryError: Java heap space。请给出你的完整排查思路和常用工具命令。

参考答案要点
  • 先用 jstat -gcutil <pid> 1000 观察各代占用与 GC 次数,确认 Full GC 后老年代不降(说明有对象被强引用持有)
  • 用 jmap -dump:live,format=b,file=heap.hprof <pid> 或 arthas heapdump 导出堆快照(注意 dump 的 STW 风险,优先摘流量)
  • 用 MAT 分析:Leak Suspects 报告 + Dominator Tree(支配树)找最大对象,沿 GC Roots 引用链定位持有者
  • 常见根因:静态集合做缓存只增不减、ThreadLocal 在线程池场景未 remove、监听器/回调未注销、批量查询一次性加载大结果集
  • 平时可开启 -XX:+HeapDumpOnOutOfMemoryError 留事故现场;能提 arthas 的 vmtool/dashboard/profiler 为加分项
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)

q206 · 困难

监控显示服务 Young GC 频率高达每秒多次,接口毛刺明显。请分析频繁 Young GC 的可能原因,并说明你的排查与调优步骤。

参考答案要点
  • 用 jstat -gcutil / GC 日志确认新生代大小、Eden 与 Survivor 比例、单次 GC 耗时与晋升量
  • 原因一:新生代(Eden)过小导致频繁触发,可调大 -Xmn 或让 G1 自适应
  • 原因二:业务分配速率过高,如循环内创建大量临时对象、一次性加载大列表、大对象直接进入老年代(PretenureSizeThreshold/G1 Humongous)
  • 原因三:Survivor 过小或动态年龄判定导致对象过早晋升,反而引发 Full GC,需结合晋升数据判断
  • 用 arthas profiler/分配栈采样定位分配热点代码,治本靠降低分配(复用、分页、流式处理)而非只调参
来源:[javabetter.cn](https://javabetter.cn/sidebar/sanfene/jvm.html)