【问题标题】:Possible Memory Leak in Java ArrayList IteratorJava ArrayList 迭代器中可能的内存泄漏
【发布时间】:2015-12-21 10:57:11
【问题描述】:

我正在用 Java 开发一个简单的游戏,只是为了用作玩具程序来为我的学生教授一些技术,但是我遇到了一些问题。我的游戏使用了两个多次迭代的 ArrayList。这些列表包含船舶发射的射弹和这些射弹可以摧毁的目标。我需要不断验证每个弹丸与屏幕中每个目标之间的碰撞,以及与遍历这些列表相关的其他事项。我注意到,当我的程序运行时,它的性能开始变得越来越差,所以我开始分析项目(我正在使用 NetBeans 分析器)以发现问题。

我发现的一件事是,使用 for each Java 来遍历列表(意味着隐式调用 iterator() 方法)开始使用大量内存但未释放。

我编写了下面的代码来测试这一点。当我对其进行分析时,ArrayList$itr 方法的内存消耗开始不断增长。列表的大小是固定的,所以我不明白为什么内存会继续增长,因为我有相同的数据结构。

看一下代码:

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

public class MemoryLeak {

    public static void main( String[] args ) {

        List<String> dummyData = new ArrayList<>();

        long quantity = 1000;
        long iterationTimeWithData = 60000;
        long iterationTimeEmpty = 10000;

        System.out.println( "adding data" );
        for ( int i = 0; i < quantity; i++ ) {
            dummyData.add( String.valueOf( Math.random() ) );
        }

        System.out.printf( "iterating through the list for %d seconds\n", iterationTimeWithData/1000 );
        long startTime = System.currentTimeMillis();
        while ( true ) {
            for ( String d : dummyData ) {}
            if ( System.currentTimeMillis() - startTime > iterationTimeWithData ) {
                break;
            }
        }

        System.out.println( "clear the list" );
        dummyData.clear();

        System.out.printf( "iterating through the empty list for %d seconds\n", iterationTimeEmpty/1000 );
        startTime = System.currentTimeMillis();
        while ( true ) {
            for ( String d : dummyData ) {}
            if ( System.currentTimeMillis() - startTime > iterationTimeEmpty ) {
                break;
            }
        }

    }

}

如果您运行代码并跟踪 ArrayList$itr,您会发现它的内存消耗在执行期间增长了很多。在我的游戏中,这种消耗是巨大的(超过 200 MB 并且还在不断增长)。使用常规 for,不会发生这种情况。

我想知道这种行为是否正确,因为对我来说这很奇怪。

【问题讨论】:

  • 垃圾收集通常在堆实际被填满之前不会启动,所以很多迭代器可能被分配并且有一段时间没有被清理也就不足为奇了。

标签: java arraylist memory-leaks


【解决方案1】:

我试图通过实验来确认 Louis Wasserman 的解释,事实上,我没有在上面的代码中观察到任何内存泄漏(使用 JDK 1.8.60)。我所做的是:

  • 将 main 方法的全部内容包装到一个 while(true) {...} 循环中,以便无限地填充、迭代和清除列表。
  • 看jvisualvm中的内存,不定时强制GC。
  • 内存有时会增长到 100MB 左右,但在 GC 之后总是下降到接近于零。

【讨论】:

    【解决方案2】:

    根据您的示例代码,行为不正确,但在意料之中。

    在使用 for-each 循环而不是简单的 for-loop 时需要非常小心。如果这段代码同时满足以下3个条件,很可能会导致内存泄漏:

    1. 使用 for-each 循环而不是简单的 for-循环
    2. for-each 循环嵌套在一个无限循环中(例如while(true){...}
    3. for-each 循环循环遍历一个集合(例如 ArrayList)而不是一个数组

    显然,您的玩具程序同时满足以上 3 个条件。

    在上述程序中发生内存泄漏是因为在迭代(next())或检查是否可以迭代(hasNext())之前,将在 for-each 循环内的集合(例如在 ArrayList 中)调用以下类似方法) 集合:

    @NotNull public Iterator<E> iterator() {
        return new Itr();
    }
    

    虽然 for-each 循环在无限循环内(即 while(true) ),这意味着将创建无限数量的 Itr 实例。最后,预计会出现内存泄漏。

    【讨论】:

      猜你喜欢
      • 2012-09-12
      • 1970-01-01
      • 2014-01-29
      • 1970-01-01
      • 2011-01-03
      • 1970-01-01
      • 2018-03-14
      相关资源
      最近更新 更多