授课语音

Java 中的堆栈溢出与内存泄漏

Java 程序在运行时会使用内存来存储数据和执行指令,但如果内存使用不当,可能会导致堆栈溢出(StackOverflowError)内存泄漏(Memory Leak)的问题。本课件将详细介绍两者的概念、成因、排查方法及解决方案,并提供代码案例和详细中文注释。


1. 堆栈溢出(StackOverflowError)

1.1 概念

StackOverflowError 是一种运行时错误,发生在程序调用栈的深度超出 Java 虚拟机分配的栈空间时。常见原因是递归调用或深层次方法调用未正确终止。

1.2 产生原因

  1. 递归调用没有终止条件
    程序中使用了递归算法,但未正确设置递归终止条件,导致递归不断深入。

  2. 栈空间大小设置不足
    JVM 默认分配的栈空间不足以支持深度调用栈。

1.3 案例代码

以下代码展示了因递归调用导致的堆栈溢出问题:

// 演示 StackOverflowError
public class StackOverflowDemo {
    public static void main(String[] args) {
        recursiveMethod(); // 调用递归方法
    }

    // 无限递归调用
    public static void recursiveMethod() {
        System.out.println("递归调用...");
        recursiveMethod(); // 自身调用导致栈溢出
    }
}

代码运行结果

递归调用...
递归调用...
...(重复输出)...
Exception in thread "main" java.lang.StackOverflowError

1.4 解决方法

  1. 增加递归的终止条件
    修改代码逻辑,确保递归调用能正常结束。
// 修正递归调用,添加终止条件
public class StackOverflowFix {
    public static void main(String[] args) {
        recursiveMethod(5); // 指定递归深度
    }

    public static void recursiveMethod(int n) {
        if (n == 0) {
            System.out.println("递归结束");
            return; // 递归终止条件
        }
        System.out.println("递归深度: " + n);
        recursiveMethod(n - 1); // 递归调用
    }
}
  1. 调整 JVM 栈大小
    使用 JVM 参数 -Xss 增加栈的大小。例如:
    java -Xss2m StackOverflowDemo
    

2. 内存泄漏(Memory Leak)

2.1 概念

内存泄漏是指程序在运行过程中动态分配的内存未被及时释放,导致可用内存减少甚至耗尽,最终引发 OutOfMemoryError

2.2 产生原因

  1. 长生命周期对象持有短生命周期对象的引用
    某些对象的生命周期比其他对象长,但它们不必要地持有短生命周期对象的引用。

  2. 未释放的静态集合
    静态集合如 MapList 存储了大量不再使用的对象。

  3. 错误的事件监听或回调
    注册的事件监听器或回调未及时移除,导致对象无法被回收。

2.3 案例代码

以下代码展示了由于静态集合未及时清理导致的内存泄漏问题:

import java.util.ArrayList;
import java.util.List;

// 演示内存泄漏
public class MemoryLeakDemo {
    private static final List<Object> STATIC_LIST = new ArrayList<>();

    public static void main(String[] args) {
        while (true) {
            Object obj = new Object(); // 创建新对象
            STATIC_LIST.add(obj);     // 将对象添加到静态集合中
            System.out.println("对象数量: " + STATIC_LIST.size());
        }
    }
}

代码运行结果

程序将持续运行,直到抛出如下异常:

java.lang.OutOfMemoryError: Java heap space

2.4 解决方法

  1. 及时清理集合中的无用对象
    定期移除集合中的无用元素。
// 修正内存泄漏问题
import java.util.ArrayList;
import java.util.List;

public class MemoryLeakFix {
    public static void main(String[] args) {
        List<Object> temporaryList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            Object obj = new Object(); // 创建新对象
            temporaryList.add(obj);   // 添加到临时集合
        }
        // 清理集合,允许垃圾回收器回收内存
        temporaryList.clear();
        System.out.println("对象清理完成");
    }
}
  1. 弱引用机制
    使用 WeakReferenceSoftReference 处理长生命周期对象对短生命周期对象的引用。

  2. 正确移除监听器
    在不再需要时,及时注销事件监听器或回调。


3. 堆栈溢出与内存泄漏的区别

区别点 堆栈溢出(StackOverflowError) 内存泄漏(Memory Leak)
发生位置 调用栈 堆内存
主要原因 递归深度过大或方法调用层次过多 不必要的引用导致内存未被回收
典型表现 程序终止,抛出 StackOverflowError 内存逐渐耗尽,最终抛出 OutOfMemoryError
解决方法 优化递归逻辑或增加栈空间 减少无用引用,清理内存

通过本课件,学员应该能够明确堆栈溢出和内存泄漏的成因与解决方法,并能够利用代码案例熟悉问题排查和优化技巧。

去1:1私密咨询

系列课程: