Java内存泄漏是本该被回收的对象因未释放引用而长期驻留堆中,引发GC效率下降和OOM;需在编码阶段通过避免静态集合滥用、正确处理内部类引用、及时关闭资源、成对管理监听器等主动预防。

Java内存泄漏不是“内存用完了”,而是本该被回收的对象因某些引用未释放,长期滞留在堆中,拖慢GC效率、最终引发OOM。防守编程的核心,不是等问题爆发再排查,而是在编码阶段主动切断泄漏路径。
静态集合类是头号泄漏源
静态变量生命周期与类加载器一致,其中存放的对象不会随业务对象销毁而释放。比如用static Map<String, Object>缓存用户会话数据,却忘了在登出时remove对应key,或没设LRU淘汰策略,缓存越积越多。
- 避免直接用
static List/Map存业务对象;必须使用时,优先选WeakHashMap或ConcurrentHashMap配合定时清理逻辑 - 若缓存需强引用,务必配套提供显式清理入口(如
clearUserCache(String userId)),并在关键流程(登出、超时)中调用 - 用IDEA或SonarQube检查静态字段赋值,尤其警惕“new ArrayList()”“new HashMap()”出现在static上下文
内部类持有外部类引用要小心
非静态内部类默认持有所属实例的隐式引用。若该内部类对象(如Runnable、Listener)被长期持有(注册到线程池、事件总线、静态监听器列表),外部类实例就无法被回收——哪怕外部类本身已无其他引用。
- 不需要访问外部类成员时,一律声明为
static class - 必须用非静态内部类时,检查其是否可能脱离外部类生命周期存在;若会,改用弱引用包装:
new WeakReference<>(this),并在内部类中通过get()安全访问 - Android开发中尤其注意Handler、AsyncTask,推荐用
static Handler + WeakReference<Activity>模式
资源未关闭导致关联对象驻留
InputStream、Connection、ResultSet等资源本身可能不大,但背后常关联着本地内存(DirectByteBuffer)、连接池句柄、甚至整个数据库连接上下文。未close不仅耗系统资源,还可能让JVM无法释放关联的Java对象。
立即学习“Java免费学习笔记(深入)”;
- 所有实现
AutoCloseable的资源,强制用try-with-resources语法,杜绝手动finally中遗漏close - 对第三方SDK返回的“伪资源”(如某些RPC客户端的Channel、Netty的ChannelFuture监听器)同样需确认释放契约,查阅文档或源码,必要时封装成AutoCloseable
- 用JProfiler或VisualVM开启“Allocation Tracking”,观察频繁创建又未释放的资源类实例
监听器和回调注册后必须解注册
GUI框架(Swing/JavaFX)、事件总线(Guava EventBus、Spring Event)、以及自定义观察者模式中,注册监听器本质是将监听对象放入某个全局容器。若忘记unregister,监听器及其持有的上下文(Activity、Fragment、Service)将一直存活。
- 注册和解注册成对出现,写代码时立刻补上unregister调用,不要“等后面再加”
- 在生命周期明确的组件中(如Servlet、Spring Bean),利用
@PreDestroy或destroy()方法统一清理 - 用弱监听器模式:自定义
WeakEventListener,内部用WeakReference持有listener,避免阻断GC
内存泄漏不靠运气发现,靠习惯预防。每一次new、每一次put、每一次addListener,都是潜在的引用锚点。把引用关系当成API契约来设计,比堆转储分析快十倍。



















