关键在于让对象及时销毁而非依赖gc,需定义退出契约、用弱引用替代强持有、监控生命周期、切断静态容器隐式绑定。

防止业务逻辑层因生命周期管理失控导致循环引用逃过检查,关键在于让“该死的对象及时死掉”,而不是依赖 GC 被动回收或静态分析侥幸捕获。这类问题往往在测试环境不暴露,上线后内存缓慢增长、响应延迟上升,最终在压测或大促时崩出 OOM —— 因为循环引用本身不报错,只是让对象图长期滞留堆中。
明确业务上下文边界,拒绝“游离态”存活
两个业务实体(如 OrderContext 和 PaymentHandler)互相强引用并不可怕,可怕的是它们完成核心流程后,仍被监听器列表、事件总线缓存、未清理的 ThreadLocal 或静态 Map 持有。
- 每个业务操作入口(如 createOrder())应配套定义明确的“退出契约”:谁负责销毁、何时触发、失败是否重试
- 禁止在 service 方法里直接 new Listener 并注册到全局 EventBus;改用带自动注销语义的封装,例如:
EventBus.registerScoped(listener, scopeId),scopeId 关联请求 ID 或事务 ID,结束后统一 unbind - 对所有注册型操作加日志埋点,记录注册来源类、时间戳、预期生命周期,便于 dump 后反查“谁忘了删”
用弱引用+显式生命周期控制替代强持有
当双向访问确属必要(如 EntityA 需调用 EntityB 的状态校验,EntityB 又需回调 EntityA 的更新通知),强引用闭环必须打破。
- 将 EntityB 的引用在 EntityA 中声明为 WeakReference
,每次访问前判空 + get();避免在构造时就建立强链 - 若需保障回调可达性,改用 PhantomReference + ReferenceQueue 实现“对象即将被回收”时的清理钩子,主动从监听容器中移除自身
- 在 EntityA/B 的 finalize() 或 cleaner 中添加断言日志(如 “destroy() not called before GC”),暴露未走正常释放路径的实例
构建可验证的生命周期行为
不能只靠代码约定,要让生命周期管理变成可观测、可断言、可回归的行为。
- 在单元测试中模拟完整生命周期:new → use → destroy → assert no strong reference path remains(可用 MAT CLI 或 jol 分析对象图)
- 使用 Arthas watch 命令监控关键类的构造与 finalize 调用频次,对比是否 1:1;不匹配即存在泄漏苗头
- 在 CI 流程中加入 heap dump 自动比对:启动后 dump 一次,执行典型业务流后再 dump,用 MAT Script 检查目标类 Retained Heap 是否归零
切断长生命周期容器的隐式绑定
很多循环引用逃过检查,是因为中间层“帮忙记住了不该记的东西”。
- 审查所有 static Map/ConcurrentHashMap:key 是否用了业务对象实例(而非业务 ID)?value 是否持有整个服务上下文?
- 禁用 FastJSON/Jackson 全局反序列化缓存(如 ParserConfig.getGlobalInstance().setAutoTypeSupport(true)),改用按需配置的 ObjectReader,避免 Class→Deserializer 映射锁死对象图
- RPC 客户端回调容器、AOP 代理工厂、自定义 Scope 实现等,全部要求实现 DisposableBean 或提供 clear() 方法,并在业务结束时显式调用










