这是隐性引用泄漏,源于aop环绕通知中将dto缓存至静态/单例容器,导致本该请求结束即回收的对象长期滞留堆内存。

这种情况属于典型的“隐性引用泄漏”——AOP切面本身无状态,但若在环绕通知(around)中将入参DTO缓存、记录、或意外放入静态/单例容器,就会让本该随请求结束而回收的对象长期滞留堆中。关键不在AOP机制,而在切面代码的持有行为。
确认是否真为短期高位泄漏
先排除误判:短期高位 ≠ 泄漏。需满足以下两个特征才可锁定:
- 服务刚启动或低流量时内存平稳;一旦突发大流量(如压测、活动开抢),堆内存**快速冲高且Full GC后无法回落到基线**(例如从200MB涨到900MB,GC后仍卡在850MB)
- 流量退去后,内存**不随请求结束而释放**,持续数分钟甚至更久才缓慢下降,或完全不降
聚焦AOP切面中的高危写法
检查所有@Around方法,重点排查以下模式:
-
静态集合缓存入参:如
private static final List<requestdto> LOG_BUFFER = new ArrayList();</requestdto>并在切面里add(dto) -
单例Bean内持有引用:比如切面注入了一个
@Service类,该类内部用ConcurrentHashMap<string requestdto></string>暂存DTO用于异步审计,但key未清理或过期策略失效 - 未关闭的流式日志/监控上报:DTO被序列化成JSON后传给异步日志框架(如Logback AsyncAppender),而DTO含大byte[]或Base64字段,导致缓冲区堆积
-
异常处理中保留引用:catch块里把dto塞进自定义错误上下文(如
ThreadLocal<map></map>),但未在finally清除
快速验证与定位手段
不依赖重启,现场抓取关键证据:
- 触发一次典型高流量场景,等内存升至70%+后,立即执行:
jmap -dump:format=b,file=aop-leak.hprof <pid></pid> - 用MAT打开hprof,按
Group by package,重点关注你项目包名下的RequestDto及其实例数量;再点开Dominator Tree,看谁在强引用它(大概率指向你的切面类、某个单例Service或静态工具类) - 同时开启GC日志(加
-XX:+PrintGCDetails -Xloggc:gc.log),观察大流量期间是否出现“老年代增长快、Full GC回收量极少”的组合信号
修复与防护建议
根本原则:DTO是请求生命周期对象,绝不跨请求存活。
- 切面内如需临时使用DTO,确保所有变量为局部变量,避免赋值给任何static、成员变量或注入Bean的字段
- 若必须记录DTO内容,只提取必要字段(如id、code、耗时),而非整个对象引用;大字段(文件内容、图片base64)一律脱敏或跳过记录
- 使用弱引用容器做临时缓存(如
WeakHashMap<object object></object>),或明确设置LRU上限+超时自动清理 - 在切面
finally块中显式清空所有中间容器,并配合@PreDestroy清理单例Bean内的缓存











