面灵AI→

波克二面:JVM、OOM 排查与用 Agent 做监控分析

轮次
二面
时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. 介绍一下 JVM。
  3. 了解 Java 垃圾回收机制吗?介绍一下。
  4. CMS 怎么找到那些需要回收的对象?
  5. 程序出现了 OOM 如何排查?
  6. 你说查看 jstat 时如果老年代比例下降是什么意思?不下降又是什么意思?
  7. 你遇到的真实 OOM 情况是为什么?List 里存了很多对象、ArrayList 不再使用了,但里面的对象一直被引用,这算内存泄漏对吗?
  8. OOM 能用 AI 分析出来吗?怎么做?
  9. 设计一个 Agent 专门用来做 OOM 或其他问题的监控指标排查,你会怎么设计?
  10. Agent 在使用设计好的工具调用时有没有可能出现幻觉?一直认为是某一类 OOM、调用重复工具怎么解决?
  11. 最近两个月遇到最难的问题是什么?
  12. 你每天开发使用 AI 大概用多少 Token?

《参考解析》

CMS 怎么找到需要回收的对象:前提是 JVM 用可达性分析而不是引用计数——从 GC Roots(虚拟机栈里的局部变量、方法区静态变量与常量引用、JNI 引用、活跃线程等)出发遍历引用链,不可达的对象才判定可回收。CMS 的难点在于「并发标记」:标记阶段用户线程还在跑,会不断产生新的引用关系,如果不管,就可能把「并发期间新引用上的对象」漏标成垃圾(三色标记里的漏标问题)。CMS 用增量更新(incremental update)+ 写屏障解决:并发标记期间,凡是把引用从黑对象指向白对象(新挂上引用)的写操作,都通过写屏障记录下来,重新标记(remark)阶段再以这些记录为根重新扫一遍,所以 remark 虽然会 STW 但很短。可以顺手对比 G1 用的 SATB(原始快照):它记录的是「引用被删除前的旧值」,关注的是并发期间断开的引用,二者解决的是同一个漏标问题的两个方向。常见的追问是:为什么 remark 比 initial mark 慢(要处理写屏障队列和整个堆的残留标记)、并发清理阶段新产生的垃圾怎么办(浮动垃圾,留到下次收集)、以及 CMS 的碎片问题(标记清除不整理,碎片多了会退化成 Serial Old 单线程 Full GC)。

OOM 的排查路径:先分类,再取证。分类靠异常信息与监控:Java heap space 是堆,Metaspace 是类元数据(多半是动态代理/热部署类加载器泄漏),GC overhead limit exceeded 是回收效率过低,unable to create new native thread 是线程数触顶,还有直接内存溢出。取证工具链是固定的:jstat -gcutil <pid> 1000 看各区占用、GC 次数与耗时;jmap -histo:live 看对象直方图找到数量或体积异常增长的类;提前配好 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=... 拿到 dump,用 MAT 或 JProfiler 看支配树(dominator tree)和「Path to GC Roots」,看是谁在持有它;线上不便 dump 时可以用 jcmd 触发、或先看 NMT 判断是否堆外。定位到持有者后,再区分是「内存泄漏」还是「内存不足」:泄漏是对象本该回收却仍被引用,典型来源是静态集合当缓存、监听器与回调没注销、ThreadLocal 用完没 remove(线程池里尤其致命)、连接与流没关、缓存没有容量上限。题目里的例子就是标准泄漏——ArrayList 这个容器虽然不再使用了,但只要它还被某个长期存活的对象(静态变量、单例里的字段、还没结束的 ThreadLocal)引用着,里面的元素就都不可回收,容器本身也就一起留着。最后一步是修:有界缓存加淘汰策略、用弱引用、把 ThreadLocal 放进 finally 清理,并用压测或长稳回归验证不再增长。

jstat 老年代占比变化怎么读:要结合绝对值和趋势一起看。每次 Full GC 之后老年代使用量下降,说明这一轮确实回收掉了对象,属于正常涨落——业务在跑,对象在晋升、被回收,占比呈锯齿形波动是健康的。如果连续几次 Full GC 之后老年代占用不降反升(面试官说的「不下降」),说明对象在被持续晋升却回收不掉,两种可能:一是参数不合理,新生代太小或晋升阈值(-XX:MaxTenuringThreshold、动态年龄判定)导致短命对象过早进老年代,表现为「GC 频繁但不泄漏」,调大新生代或换 G1 观察是否缓解;二是真的在泄漏,对象被长期引用,只能靠 dump 找引用链。判断分水岭是「占用是否持续单调上升、Full GC 频率是否越来越密、单次 Full GC 后回收量是否越来越小」,三条同时成立基本可以判定泄漏。补充一句:占比高但稳定、GC 耗时可控,通常不是问题(很多服务常年 70% 以上),不要只凭一个百分比下结论。

设计一个做 OOM 排查的 Agent,以及怎么抑制它的幻觉:核心思路是把「确定性的事情交给程序,判断交给模型」。工具集按排查路径配:拉取 GC 与内存指标(jstat/JMX/监控系统)、拉对象直方图 topN、拉 dump 的摘要(支配树前 N 项、增长最快的类、到 GC Roots 的引用链)、查最近的发布与配置变更、检索同类历史工单。绝对不要把原始 dump 直接喂给模型——几十 GB 的数据既是 token 灾难也没必要,先在工具侧做确定性预处理,只把结构化摘要送进上下文。流程上做成状态机而不是自由对话:第一步分流(堆 / 元空间 / 直接内存 / 线程),第二步在分支内按清单取证,每步记录「已获得哪些证据、排除了哪些假设」,最后输出结论 + 证据引用 + 建议动作。抑制幻觉有五个抓手:① 每个结论必须绑定具体证据(工具返回的字段与时间点),没有证据不许下结论,输出里强制带上引用;② 能用规则判定的前置事实就用解析器判定(比如「Full GC 后老年代不降」由 jstat 解析器给出布尔值),不让模型去猜数字;③ 重复调用检测:对「工具名 + 参数」做哈希,命中已执行过的调用就拒绝并提示模型换策略,同时把已排除的假设写进状态,避免它在同一个方向上打转;④ 明确的停止条件与置信度,证据不足时输出「需要人工介入 + 建议补充哪个指标」,而不是硬给一个分类;⑤ 用积累了标注结论的历史 case 做回归评测,每次改 prompt 或换模型都跑一遍,用数据决定改动是否有效。这套做法同样适用于其他线上问题的自动排查,区别只是工具集和分流树。