本质是静态集合长期持有本该短暂存在的JIT编译快照等诊断对象,导致老年代持续膨胀并触发Full GC;其特征为类名含JIT/Compilation/Stack、持有大数组或深层调用链、被static集合强引用且随运行时间线性增长。

这类问题本质是静态集合长期持有本该短暂存在的诊断类对象,比如 JIT 编译快照、完整堆栈封装体、动态生成的调试上下文等。它们体积大、引用深、生命周期远超预期,持续堆积在老年代,最终触发 Full GC。
识别是否为静态集合误存导致
先排除常规内存问题:不是大缓存未清理、不是日志对象堆积、也不是流或连接未关闭。重点看堆中是否存在以下特征:
- 类名含 JIT、Compilation、StackFrame、HotSpot、CodeCache、Diagnostic 等关键词
- 对象内部持有
char[]、byte[]或嵌套多层StackTraceElement[],单个实例占用几 MB - 被
static final Map、static ConcurrentHashMap或static List直接强引用,且该集合实例数恒为 1 - 对应对象数量或总大小随运行时间线性增长(对比多次
jmap -histo:live结果)
快速定位绑定关系
不用等 dump,用基础命令就能缩小范围:
- 执行
jmap -histo <pid> | grep -E "(JIT|Compilation|Stack|Diagnostic)",看是否排进前 10 - 再运行
jmap -histo:live <pid>,隔几分钟重复一次,观察目标类实例数/总容量是否持续上升 - 找到疑似静态集合类(如
TraceRegistry.cache),反查其所在 jar 包和代码路径,确认是否在 AOP 切面、监控埋点、编译事件监听器中被调用
检查典型误用场景
问题多出现在诊断增强模块,常见写法包括:
- 在
@Around切面中直接调用Thread.currentThread().getStackTrace(),未过滤ClassLoader.defineClass、Unsafe.defineAnonymousClass等 JIT 内部帧 - 用
ManagementFactory.getCompilationMXBean()获取编译信息后,把返回的CompositeData或自封装对象塞进静态 map,且无过期机制 - 为每个方法调用生成
CompilationTrace并以方法签名作 key 存入全局ConcurrentHashMap,但 key 无去重、value 无大小限制
验证与轻量修复
上线前可快速验证并缓解:
- 加 JVM 参数
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation,观察日志中编译事件频率是否与 Full GC 时间点高度重合 - 在静态集合的
put调用处加日志,打印key和value.getClass().getName() + " size:" + sizeOf(value) - 临时禁用相关诊断逻辑;或改为弱引用缓存 + 容量上限 + LRU 驱逐;或只缓存摘要(如 hash 值、帧数、最外层类名),不存完整堆栈

















