davidbuzatto
davidbuzatto

Reputation: 9424

Possible Memory Leak in Java ArrayList Iterator

I'm developing a simple game in Java just to use as a toy program to teach some techniques for my students, but I'm having some problem. My game uses two ArrayList that are iterated lots of times. These lists contains the projectiles that a ship fire and the targets that these projectiles can destroy. I need to constantly verify the colision between each projectile with each target in the screen among other things related to iterating throught these lists. I noted that while my program was running, the performance of it started to get worse and worse, so I started to profile the project (I'm using NetBeans profiler) to find problems.

One thing that I find is that using the for each of Java to iterate throught the lists (implies calling iterator() method inplicitly) a lot of memory started to be used and not released.

I wrote the code below to test this. When I profile it, the ArrayList$itr method memory consumption starts to grow and grow. The list has a fixed size, so I don't understand why the memory continues to grow, since I have the same data structure.

Take a look in the code:

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;
            }
        }

    }

}

If you run the code and keep track of ArrayList$itr you will see that its memory consumption grows a lot during the execution time. In my game, this consumption is enormous (more than 200 MB and keeps growing). Using a regular for, this does not occur.

I would like to know if this behavior is correct, because for me its very strange.

Upvotes: 3

Views: 2540

Answers (2)

garykwwong
garykwwong

Reputation: 2427

per your sample code, the behaviour is incorrect, but expected.

It needs to be very careful while using for-each loop instead of simple for-loop. If the code piece fulfills the following 3 conditions at the same time, it would probably leads to memory-leak:

  1. using for-each loop instead of simple for-loop
  2. the for-each loop is nested inside an infinite loop (e.g. while(true){...})
  3. the for-each loop is looping over a collection (e.g. ArrayList ) instead of an array

Apparently, your toy program has fulfilled above 3 conditions at the same time.

Memory-leak occurs in above program because the following similar method will be called on the collection (e.g. in ArrayList ) inside the for-each loop before iterating ( next() ) or before checking whether could iterate over ( hasNext() ) the collection:

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

While the for-each loop is inside an infinite loop (i.e. while(true) ), which means infinite number of Itr instance will be created. Finally, memory-leak is expected.

Upvotes: 1

Jan X Marek
Jan X Marek

Reputation: 2504

I tried to confirm Louis Wasserman's explanation experimentally, and indeed, I did not observe any memory leak in the code above (with JDK 1.8.60). What I did was:

  • Wrap the entire content of the main method into a while(true) {...} loop, so that the list gets filled, iterated through, and cleared ad infinitum.
  • Watch the memory in jvisualvm, force GC from time to time.
  • The memory sometimes grew to 100MB or so, but it always dropped close to zero after GC.

Upvotes: 1

Related Questions