内存分配

在 JVM 中,内存泄漏(Memory Leak)的核心定义是:存在一些对象,它们已经不再被程序使用(没有任何业务意义),但由于仍然被 GC Roots(根对象)直接或间接引用,导致 GC(垃圾回收器)无法回收它们,最终占用的内存越来越多,直至引发 OOM(内存溢出)。

内存泄漏是 “缓慢积累” 的过程,而 OOM 是泄漏的最终结果之一

堆内存泄漏

堆内存用于存储对象实例,当对象 “无用但可达” 时,就会发生泄漏

  • 静态集合(如 static List/Map)的生命周期与 JVM 一致。如果向静态集合中添加对象后,未在使用完后移除,这些对象会一直被集合引用,导致无法回收。
  • 文件流(FileInputStream)、数据库连接(Connection)、网络连接(Socket)等资源对象,不仅占用堆内存,还关联底层系统资源。如果未调用 close()释放,这些对象会被 JVM 持有引用(如通过 finalize 机制等待回收),但回收周期长,且可能因资源句柄耗尽导致泄漏。
  • 非静态内部类(成员内部类)会隐式持有外部类的实例引用。如果内部类对象被长期引用(如放入静态集合),会导致外部类实例也无法被回收,即使外部类已经无用
  • 使用 HashMap 等容器做缓存时,如果只添加缓存不清理过期数据,缓存会无限增长。即使缓存的 key/value 已无用,只要被缓存引用,就不会被 GC 回收。
  • ThreadLocal 用于线程内共享变量,其内部通过 Thread 的 threadLocals(一个 ThreadLocalMap)存储数据。如果 Thread 长期存活(如线程池中的核心线程),且 ThreadLocal 对象被回收,但 ThreadLocalMap 中的 Entry 未被清理(key 为 null,但 value 仍存在),会导致 value 对象泄漏。

解决

及时释放资源,慎用静态集合,合理管理缓存

内存溢出

内存溢出是指 JVM 在尝试分配内存时,发现目标内存区域已无足够空间,且垃圾回收(GC)后仍无法释放出可用空间,最终抛出 java.lang.OutOfMemoryError 异常

溢出场景

  1. 堆内存溢出:
    • 短时间内创建大量对象,堆内存不足以容纳
    • 内存泄漏长期积累,堆被占满
    • 堆配置过小
  2. 方法区溢出: + 频繁动态生成类 + 常量池过大
    等等,除程序计数器外,所有区域都可能发生溢出