本质是静态集合长期持有JIT编译期生成的堆栈快照对象(如CompilationInfo、JITStackFrame等),导致老年代持续膨胀并触发Full GC;其特征为类名含JIT/Compilation/Stack、持有大数组或深层调用链、被static集合强引用,非典型内存泄漏但危害类似。

这种情况本质是“静态集合长期持有不该持有的对象”,而对象本身又携带大量 JIT 编译期生成的堆栈快照(如 CompilationInfo、JITStackFrame 或自定义封装的带完整调用链的诊断对象),导致老年代持续膨胀,最终触发 Full GC。它不是典型内存泄漏,但危害类似——对象生命周期远超预期,且单个实例体积大、引用链深。
确认是否真由 JIT 相关对象引发
先排除常规嫌疑:不是大缓存、不是日志堆积、不是未关闭的流。重点看堆中是否存在以下特征对象:
- 类名含
JIT、Compilation、HotSpot、StackFrame、CodeCache等关键词 - 对象内部持有
char[]、byte[]或嵌套多层StackTraceElement[],总大小动辄几 MB - 被一个
static final Map/ConcurrentHashMap或static List直接强引用
定位静态集合与 JIT 对象的绑定关系
用 jmap -histo 快速筛查:
- 执行
jmap -histo <pid> | grep -E "(JIT|Compilation|Stack)",看是否排在前 10 - 再查该类的直接持有者:
jmap -histo:live <pid>后比对,确认是否随运行时间线性增长 - 若发现某
static集合(如DiagnosticRegistry.cache)实例数恒为 1,但其 value 中的 JIT 对象数量/大小持续上升,基本可锁定
检查代码中是否误将 JIT 上下文写入业务缓存
这类问题常出现在诊断增强模块中,例如:
- 为每个方法调用自动采集 JIT 编译堆栈,并存入全局
ConcurrentHashMap<String, CompilationTrace>,但未设置过期或容量上限 - 使用了
ManagementFactory.getCompilationMXBean()获取实时编译信息,却把返回的CompositeData或封装对象塞进静态 map - 自研 AOP 切面在
@Around中捕获Thread.currentThread().getStackTrace()并缓存,且未过滤 JIT 内部帧(如java.lang.ClassLoader.defineClass、sun.misc.Unsafe.defineAnonymousClass等)
验证与修复建议
不依赖 dump 也能快速验证:
- 临时加 JVM 参数:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation,观察日志中是否频繁输出编译事件,再比对 GC 时间点是否吻合 - 在疑似静态集合的
put调用处加断点或日志,打印 key 和 value 的toString()或getClass().getName() + " size:" + sizeOf(value) - 修复方向:禁用非必要 JIT 信息采集;改用弱引用/软引用缓存;添加 LRU 驱逐策略;或直接移除该缓存逻辑——JIT 信息本就不该进入业务生命周期

















