优化业务代码降低对象图深度可显著缩短G1/ZGC并发标记耗时,核心是减少长生命周期对象对短生命周期对象的强引用、拆解嵌套结构、避免隐式持有、及时断开无用引用,并配合JVM日志与JFR验证效果。

通过优化业务代码降低对象图深度,能显著减少 JVM 垃圾回收(尤其是 G1 或 ZGC 的并发标记阶段)需要遍历的引用链长度和节点数量,从而缩短标记耗时。核心思路是:**减少长生命周期对象对短生命周期对象的强引用、拆解深层嵌套结构、避免隐式持有、及时断开无用引用**。
精简对象图层级,避免“一挂全挂”
常见问题:一个全局缓存 Map 持有 Service 实例,Service 又持有一堆 Repository、Config、Helper 等,而这些对象又各自引用更多子对象——哪怕只用其中某个字段,整个深图都会被标记器扫描。
- 把大对象拆成职责单一的小对象,按需加载。例如,不用让 User 对象直接包含 Address、OrderHistory、Preference 等完整实体,改用 ID + 懒加载代理或单独查询
- 对非核心上下文数据,改用弱引用(WeakReference)或软引用(SoftReference)缓存,使其在并发标记期间可被跳过或快速释放
- 避免在 ThreadLocal、静态集合、监听器列表中长期持有业务对象引用;使用后显式 remove(),尤其注意 Web 请求线程复用场景
收敛长生命周期对象的引用范围
Spring Bean 默认单例,若它强引用了大量请求级对象(如 DTO、VO、临时计算结果),就会把本该快速回收的对象拖入老年代,延长并发标记路径。
- 将非状态性逻辑抽离为无状态工具类(static 方法或 prototype bean),不持有任何业务数据
- 用方法参数传递上下文,而非通过 this 引用层层透传;避免 “ContextHolder” 类长期持有多层嵌套 DTO
- 对异步任务(如 CompletableFuture、@Async),显式清理闭包捕获的外部对象,必要时用局部变量复制关键字段,切断引用链
识别并剪除隐式强引用
某些看似无害的写法会悄悄延长对象存活期,增加标记负担:
- 内部类默认持有外部类 this 引用 → 改为 static 内部类 + 显式传参
- lambda 表达式捕获了大对象(如整个 controller 实例)→ 提取需用字段,避免直接引用 this 或成员变量
- Stream 链式调用中,中间操作(如 map、filter)若引用了外部集合或服务,可能造成意外保留 → 优先用无状态函数式接口,或提前 .toList() 截断流
配合 GC 调优验证效果
代码优化后,需用真实负载验证是否真正降低了标记时间:
- 开启 JVM 标记日志:-Xlog:gc+phases=debug -Xlog:gc+ref=debug,观察 Concurrent Mark 和 Remark 阶段耗时变化
- 用 JFR(Java Flight Recorder)录制运行时,分析 “Object Count” 和 “Reference Chain” 事件,定位深度 >5 的热点引用路径
- 对比优化前后 Eden 区晋升率与老年代增长速度——对象图变浅后,短命对象更易在 Minor GC 中被回收,减少并发标记压力

















