Java ArrayList 迭代器可能存在内存泄漏问题

3
我正在开发一个简单的Java游戏,只是用作教授一些技术给我的学生的玩具程序,但我遇到了一些问题。我的游戏使用两个ArrayList,这些列表被迭代很多次。这些列表包含飞船发射的抛射物和这些抛射物可以摧毁的目标。我需要不断地验证屏幕上每个抛射物与每个目标之间的碰撞,以及与迭代这些列表相关的其他事项。我注意到,在我的程序运行时,它的性能开始变得越来越差,因此我开始对项目进行分析(我使用NetBeans分析器)以找到问题。
我发现的一件事是,使用Java的for each来迭代这些列表(隐式调用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循环则不会出现这种情况。

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


2
垃圾回收通常不会启动,直到堆实际上被填满,因此大量的迭代器可能被分配并且一段时间内没有被清理也不一定令人惊讶。 - Louis Wasserman
2个回答

1

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

  • 将主方法的整个内容包装在while(true) {...}循环中,以便列表被填充、迭代和无限清除。
  • 使用jvisualvm监视内存,不时强制进行GC。
  • 内存有时会增长到100MB左右,但总是在GC后降至接近零。

1
根据您提供的示例代码,行为是不正确的,但是可以预期。
在使用for-each循环而不是简单的for循环时需要非常小心。如果代码同时满足以下3个条件,则可能导致内存泄漏:
1. 使用for-each循环而不是简单的for循环 2. for-each循环嵌套在无限循环中(例如while(true){...}) 3. for-each循环遍历的是集合(例如ArrayList)而不是数组
显然,您的玩具程序同时满足以上3个条件。
由于在for-each循环之前会调用集合(例如ArrayList)中的以下类似方法,以便在迭代(next())之前或在检查是否可以迭代(hasNext())之前对其进行操作,因此会发生内存泄漏:
@NotNull public Iterator<E> iterator() {
    return new Itr();
}

当for-each循环在一个无限循环内部(即while(true)),这意味着会创建无数个Itr实例。最终,会出现内存泄漏问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接