可通过g1/zgc写屏障触发频次、并发标记对象数、rset/mark bitmap开销等可观测信号定位内存暴增源头,如com.example.cache.dataholder类高频写入或ordercache.put()滥用静态map。
不能直接“反向感知”对象暴增,但可以通过 g1 或 zgc 的动态屏障行为特征,结合可观测指标,及时发现并定位异常内存增长的源头。关键不在于屏障本身报警,而在于它暴露的运行时信号——比如写屏障触发频率突增、并发标记阶段扫描对象数异常上升、或 rset 更新开销飙升等,这些都指向某些类正在高频创建或强引用堆积。
关注写屏障的触发频次与分布
G1 和 ZGC 都依赖写屏障(Write Barrier)来维护跨代/区域引用关系。当某个类的对象被大量写入(如频繁 put 到 Map、添加到 List、赋值给静态字段),对应字段写操作会触发写屏障。
- 用 JVM 自带工具采集:启动时加 -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime -Xlog:gc+ref+phases=debug,观察 GC 日志中 “Update RS”(G1)或 “ZBarrier” 相关耗时是否持续升高
- 结合 JFR(Java Flight Recorder)开启 jdk.WriteBarrierStatistics 事件,按类名聚合写屏障调用次数,暴增的类往往就是问题源头(例如
com.example.cache.DataHolder在 10 秒内触发 200 万次写屏障,远超均值) - 注意区分:普通 setter 赋值不一定触发(若目标是栈上局部变量),但写入堆内对象字段(尤其是静态容器、长生命周期对象的成员)才会激活屏障
分析并发标记阶段的对象图膨胀信号
ZGC 的并发标记、G1 的并发标记周期,本质是对整个堆做可达性遍历。若某类实例在标记过程中被反复扫描、且数量远超预期,说明它正成为 GC Roots 的“间接延伸点”。
- 启用 -Xlog:gc+mark=debug(ZGC)或 -Xlog:gc+region+age=trace(G1),查看标记日志中 “marked X objects in Y region”,对比各 Region 的标记密度;若某几个 Region 中某类对象占比 >60%,需重点检查该类的持有链
- 使用 jcmd 触发即时堆直方图:jcmd
VM.native_memory summary scale=MB + jmap -histo:live,比对两次快照间特定类的实例数增幅(如 byte[]或java.util.HashMap$Node突增 5 倍) - 特别警惕:静态集合类(
static Map<k v></k>)、线程本地缓存(ThreadLocal<list></list>)、未清理的监听器注册表——它们让对象长期可达,直接抬高标记压力
监控 RSet / Mark Bitmap 的元数据开销
G1 的 Remembered Set(RSet)和 ZGC 的 Mark Bitmap 都是屏障驱动的元数据结构。其大小和更新频率能反推跨区域引用强度。
- G1 下观察日志中 “Remembered Set” 相关统计:如 “RS scanned” 数值在 Young GC 中持续超过 100k/次,或 RSet 总内存占用突破堆的 5%,大概率存在某类对象在 Eden 区高频创建,并被老年代对象强引用(如缓存预热时批量加载)
- ZGC 下通过 jstat -zgc
查看 Mark Abort 和 Relocate Abort 次数;若频繁 abort 且伴随 Mark Stack Usage 接近上限,说明标记图深度过大——常由深层嵌套对象(如 JSON 树、AST 节点)或循环引用链引发 - 用 JFR 录制 jdk.G1RegionTypeChange 和 jdk.ZGCCycle,关联 Region 类型变更与 GC 周期耗时,识别“刚晋升就爆满”的异常区域,再回溯其中主导类
结合代码路径做轻量级注入验证
当怀疑某类时,无需改源码,可用 JVMTI 或字节码增强工具(如 Byte Buddy)在类构造器或关键方法入口插入轻量计数逻辑:
- 对疑似类
OrderProcessor的<init></init>方法,统计每秒 new 实例数;若数值与写屏障频次曲线高度吻合,基本锁定 - 在静态集合的
add()、put()方法埋点,记录调用栈深度和线程名;常可发现“定时任务线程反复初始化缓存”或“异步回调未限流导致刷入”等模式 - 配合 -XX:+HeapDumpBeforeFullGC,在 Full GC 前自动生成堆转储,用 Eclipse MAT 的 “Dominator Tree” 直接查看最大对象持有者,跳过屏障分析直击根源
本质上,屏障不是探测器,而是压力计。它的异常波动是系统在告诉你:“这里正发生违背分代假设的行为”——对象不该活这么久、不该被这么频繁修改、不该形成这么复杂的引用网。抓住这个信号,再叠加上下文指标,就能把模糊的“内存涨了”变成清晰的“是 OrderCache.put() 在每秒新建 8000 个 OrderItem 且塞进 static Map”。不复杂但容易忽略。











