java内存泄漏主因是对象被意外强引用而无法gc,常见于静态集合未清理、监听器未注销、threadlocal未remove、非静态内部类隐式持有外部实例等场景。

Java 中的内存泄漏往往不是因为忘了 free,而是对象被意外持有、无法被 GC 回收。最常见于静态集合、监听器、线程本地变量和缓存未清理等场景。
静态集合长期持有对象引用
静态 Map、List 或 Set 是泄漏高发地——只要类没卸载,里头的对象就一直活着。
- 避免直接用
static Map<string object></string>缓存业务对象;改用WeakHashMap(key 为弱引用)或ConcurrentHashMap配合定时清理逻辑 - 若必须用静态容器,确保在对象失效后显式调用
remove(),不要依赖“之后再清” - 注意:
WeakHashMap的 key 被回收后,对应 entry 不会立即消失,需触发一次 GC + map 内部清理(如调用size()或迭代)
未注销的监听器和回调
GUI 组件、EventBus、RxJava 订阅、Spring @EventListener 等,一旦注册但未解绑,就会让监听目标(通常是 Activity、Fragment、Service 或 Controller 实例)无法释放。
- Android 中务必在
onDestroy()或onDetachedFromWindow()里调用removeXXXListener() - JavaFX / Swing 同理,在
dispose()或窗口关闭时清理 - 使用弱引用监听器库(如
guava-weak-event-listener)可降低风险,但不能替代主动管理
ThreadLocal 未清理导致的泄漏
在线程池复用场景下(如 Tomcat、Dubbo、自建 ThreadPoolExecutor),ThreadLocal 变量若未手动 remove(),其 value 会随线程存活而长期驻留,且 value 若强引用了外部对象(如 ServletRequest、Bean 实例),就会连带泄漏。
立即学习“Java免费学习笔记(深入)”;
- 永远在
finally块中调用threadLocal.remove(),不要只靠set(null) - 避免在
ThreadLocal中存大对象(如 byte[]、List)或上下文全量副本 - 排查时注意:线程池中的 worker thread 名称通常含
pool-,用 jstack 或 VisualVM 查看其ThreadLocalMap内容
内部类与匿名类隐式持有所属实例
非静态内部类、Lambda 表达式默认持有外部类 this 引用。若该内部类被长期持有(如传给线程、静态变量、异步回调),外部实例就无法回收。
- 优先使用静态内部类 + 显式传参;若需访问外部成员,用
WeakReference<outer></outer>包装 - Lambda 在引用实例方法时(如
obj::method)也会捕获obj,注意生命周期匹配 - Android 中尤其危险:Handler、AsyncTask、Runnable 放在非静态内部类里,极易造成 Activity 泄漏
内存泄漏最难调试的地方在于:它不报错,只缓慢吃掉堆空间,直到 OOM 才暴露。真正有效的手段不是等异常,而是从代码设计阶段切断长生命周期对象对短生命周期对象的强引用链。










