从源头减少内存泄漏需在编码时切断强引用链:禁用裸static集合,优先用caffeine或weakhashmap;spring单例集合、threadlocal(须finally remove)、资源(用try-with-resources)、监听器(注册/注销成对且避免隐式引用)均须规范管理。

从源头减少内存泄漏,关键不是等问题出现再排查,而是在写代码时就切断不该存在的强引用链。规范习惯要落在日常编码动作里,不靠记忆、不靠事后补救。
静态集合必须带清理机制或弱语义
静态 Map、List 是泄漏头号元凶,因为它们生命周期与类加载器一致,里面对象只要没被显式移除,GC 就永远收不走。
- 禁用裸 static HashMap/ArrayList 做缓存,哪怕只是临时存几个对象
- 优先用 Caffeine:自动过期、容量限制、LRU 淘汰,一行配置就能防住无限增长
- 若场景简单(如 key 本身可被回收),改用 WeakHashMap;注意它不主动清理 entry,需配合 size() 或迭代触发内部清理
- Spring 单例 Bean 中的成员集合,同样具备长生命周期,不能当成“局部容器”随意塞数据
ThreadLocal 必须配 finally remove()
在线程池复用环境下,ThreadLocal 不清理,上一个请求的数据会“粘”在 worker 线程上,连带持有外部对象(比如 Controller 实例、Request 对象),造成跨请求泄漏。
- 永远在 try-finally 中调用 remove(),不要只 set(null)
- 避免存大对象(如 byte[]、全量上下文 Map),能传参就别塞进 ThreadLocal
- 封装成工具方法统一管理,比如 withContext(() -> { /* 业务 */ }),内部自动 remove
资源操作强制用 try-with-resources
流、连接、通道这些不只是占堆内存,还持 Native 句柄。不关闭会导致双重泄漏:堆内对象无法回收 + 系统资源耗尽(如文件描述符打满)。
- 所有实现 AutoCloseable 的类,一律用 try-with-resources,不用手写 finally
- 多个资源用分号隔开,顺序按创建逆序关闭,避免 close() 抛异常中断后续释放
- 自定义资源类务必正确实现 close(),且保证幂等(多次调用不报错)
监听器和回调必须成对注册/注销
GUI 组件、EventBus、RxJava 订阅、Spring @EventListener、Android 广播接收器……一旦注册后没解绑,就会让监听目标(Activity、Service、Controller)一直被强引用,无法 GC。
- 注册点和注销点严格对应:onCreate/onDestroy、onStart/onStop、init/destroy
- 避免把监听器直接写成非静态内部类或 Lambda,防止隐式持有外部 this
- 确需异步回调时,用 WeakReference 包装宿主对象,或改用静态内部类 + 显式传参
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











