Java内存泄漏主要因强引用阻止GC回收,典型场景包括:1.静态集合长期持有对象;2.非静态内部类隐式引用外部类;3.未关闭资源或ThreadLocal未remove。

内存泄漏在 Java 中通常指对象不再被使用,但垃圾回收器无法回收它,因为还存在强引用。最典型的演示方式是用静态集合长期持有对象引用,且不清理。
使用静态集合持续添加对象
静态变量生命周期与类相同,只要类加载后不卸载,其中的集合就一直存在。如果不断往静态 List 里 add 新对象,又不 remove,这些对象就永远无法被 GC。
- 定义一个静态 ArrayList<Object>
- 在循环中反复创建新对象并加入该列表
- 不提供清除逻辑,也不让引用失效
示例代码:
public class MemoryLeakDemo {
// 静态集合,长期持有对象引用
private static final List<Object> cache = new ArrayList<>();
<pre class='brush:php;toolbar:false;'>public static void main(String[] args) throws InterruptedException {
System.out.println("开始内存泄漏演示...");
for (int i = 0; i < 1_000_000; i++) {
cache.add(new byte[1024 * 1024]); // 每次加1MB对象
if (i % 10000 == 0) {
System.out.println("已添加 " + i + " 个对象");
Thread.sleep(10); // 放慢速度,便于观察
}
}
System.out.println("添加完成,等待GC(但不会真正释放)");
// cache 仍持有全部引用,即使方法结束也无法回收
}}
内部类持有外部类引用导致泄漏
非静态内部类默认持有一个外部类的隐式引用。若内部类实例被静态变量或长生命周期对象(如线程池、监听器)持有,外部类也会被一起“钉住”,无法回收。
- 定义一个大对象(如含大量字段或缓存的类)
- 在其内部定义非静态 Runnable,并提交给静态线程池
- 该 Runnable 会间接持有外部类实例,造成泄漏
未关闭资源型对象(如未关闭的连接/流)
虽然严格来说属于资源泄漏,但某些场景下会导致关联对象(如缓冲区、句柄包装类)无法释放,间接引发堆内存增长。
- 用 FileInputStream 打开大文件但不调用 close()
- 使用 ThreadLocal 存储对象后未调用 remove(),尤其在线程池中复用线程时
如何验证是否发生泄漏
运行上述代码时加上 JVM 参数:-Xmx64m -XX:+PrintGCDetails,观察 Full GC 后老年代占用是否持续上升;也可用 VisualVM 或 JConsole 连接进程,查看堆内存曲线和对象实例数变化。

















