【问题标题】:Java: Memory leak strangeness with ListsJava:Lists 的内存泄漏异常
【发布时间】:2013-03-12 19:36:38
【问题描述】:

我正在对 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 接口不允许这种用法吗?我只是没有给垃圾处理足够的时间来摆脱最后一个实例吗?

【问题讨论】:

  • 注意:Thread.sleep(1) 休眠 1 毫秒,而不是 1 秒。
  • 是的,我知道。我也尝试过更长的持续时间。
  • 使用System.gc() 运行垃圾回收。睡眠没有任何作用。而且list仍然是从当前函数框架中引用的,所以可能没有被收集到。
  • VisualVM检查你的代码,内存使用率按预期上升和下降。
  • 您在任务管理器中看到的是 JVM 分配了多少内存,而不是有多少空闲/占用。通常,JVM 不愿意将内存还给操作系统,尽管这通常是可配置的。你肯定在这里猎鬼。

标签: java list loops memory-leaks


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

  • 我稍后在一个程序中做了一个类似的过程,而且时间很短,但是内存永远不会被释放。就算让程序坐在那里,内存也会慢慢堆积起来。
  • 也许你是对的。也许是任务管理器没有正确读取内存使用情况?我试着把它挂起来,这样我就可以停止创建列表然后重新开始,但是内存永远不会放开(根据任务管理器。
  • 我做了同样的事情,最后得到了一个非常相似的图表。我在 linux 上使用 java 7。
  • @MrDoctorProfessorTyler 为什么不像 NPE 那样尝试使用 VisualVM?如果您已经安装了 JDK,那么它已经在您的计算机上。
  • 听起来不错。编辑:我的机器上似乎没有它……上网!
【解决方案2】:

您需要在 while 循环中的每次迭代后将 list 设置为 null。这样,GarbageCollector 就知道它可以安全地进行清理了。

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

【讨论】:

  • 即使我这样做了,问题仍然存在。
【解决方案3】:

(通常我只会评论并询问您设置的 VM 选项,但我没有足够的代表点来执行此操作)

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

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2012-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-31
      • 2014-01-01
      • 1970-01-01
      相关资源
      最近更新 更多