1

我正在用 Java 及其允许内存泄漏的能力做一个小实验。我写了这个简单的代码:

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

public class Test {

public static void main(String[] args) {

    class Obj {
        int i;

        Obj(int i) {
            this.i = i;
        }
    }

    List<Obj> list;
    while(true) {
        list = new ArrayList<Obj>();
        for(int i = 0; i < 1000; i++) {
            Obj o = new Obj(i);
            list.add(o);
        }

        try {
            Thread.sleep(1); //<-- added to give the gc time to trash the previous iteration
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
}

执行此操作时(尤其是每次迭代都会将更多对象添加到列表中),您可以看到使用的内存量迅速增加。在每次迭代中添加 10000 个对象时,我能够非常快速地达到 2 GB。看起来好像列表(以及使用“链表”类似函数的其他类型的对象)不喜欢被删除。

当我没有将对象添加到列表中时,内存堆根本没有增加(这意味着垃圾处理正在完成它的工作)。我尝试将列表重置为 null 每次迭代然后重新声明它以及调用 clear() 方法。似乎没有任何效果。每当我在循环中使用 List 时,我的 RAM 都会呼救。

那么,为什么会出现这种情况?为什么垃圾处理不会在每次迭代时都摆脱列表而不是让它们堆积起来?List 接口不允许这种用法吗?我只是没有给垃圾处理足够的时间来摆脱最后一个实例吗?

4

4 回答 4

5

你的代码很好。除非您在 JVM 中遇到了一些晦涩且罕见的错误,否则也不会出现内存泄漏。可能发生的情况是您为 JVM 提供了大量内存来使用,并且决定使用该内存。

对您的代码进行了试验后,我可以重现您所看到的唯一方法是配置一个非常大的年轻代堆 ( -Xmn)。JVM 不需要在年轻代满之前运行垃圾收集器,因此该进程最终会使用相当多的内存。

但是,当年轻代堆满时,GC 会运行并收集所有无法访问的对象。

在以下屏幕截图中,右上角的图表显示了堆大小:

视觉虚拟机

如您所见,在 19:47:50 之前出现了急剧下降。这是年轻代填满的时候,也是 GC 运行并收集所有旧列表的时候。

请注意,如果您使用操作系统工具来监控内存使用情况,那么在运行垃圾收集器时您可能不会看到下降。当一些堆对象被释放时,它们过去占用的内存通常不会被释放回操作系统。但是,它可以被同一进程重用。

如果内存使用是一个问题,您需要重新访问您提供给 JVM 的选项。

于 2013-03-12T19:51:15.587 回答
0

您的代码中没有内存泄漏。你可以 100% 确定这一点。但是,这并不意味着即使所有当前活动的对象都小于堆,您也不会耗尽内存。

您无法使用像您这样的代码测试垃圾收集。GC 不会每次都被调用,也不会一次清理所有内容。如果您有大量数据快速刷新,您的 GC 将没有时间启动,并且您的代码可能内存不足。

同样重要的是要了解内存泄漏是使用分析工具测试的,而不是通过监视应用程序是否耗尽内存来测试的。

于 2013-03-12T19:59:12.577 回答
0

您需要在循环中的每次迭代之后设置list为。这样 GarbageCollector 知道它可以安全地清理。nullwhile

这也可以解决该行为。http://docs.oracle.com/cd/E19159-01/819-3681/abebi/index.html

于 2013-03-12T19:52:09.210 回答
0

(通常我只会评论并询问您设置的 VM 选项,但我没有足够的代表点来做到这一点)

我只是猜测,但也许您将 -Xms 设置为 1GB?此代码不会泄漏,但根据您的 VM 选项,该过程可能仍会占用大量内存。

于 2013-03-12T19:57:16.250 回答