Snippets

Radistao A JVM "for" iterator GC issue

Created by Radistao A last modified
import java.util.ArrayList;

public class GcWithIterator {

    public static void main(String[] args) {
        System.gc();
        showMemoryUsage("With iterator initial");

        final int N = 7500;
        ArrayList<String> strings = generateLargeStringsArray(N);
        showMemoryUsage("Array created");

        // this is only one difference - after the iterator creating memory is not collected any more
        for (String string : strings);

//        Note: this iterator doesn't create any garbage issue
//        Iterator<String> iterator = strings.iterator();
//        while (iterator.hasNext()) {
//            iterator.next();
//        }
//        iterator = null;

        strings = null;
        System.gc();

        showMemoryUsage("After GC");

        ArrayList<String> strings2 = generateLargeStringsArray(N);
        showMemoryUsage("After second array creation");
    }

    private static ArrayList<String> generateLargeStringsArray(int N) {
        ArrayList<String> strings = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            StringBuilder sb = new StringBuilder(N);
            for (int j = 0; j < N; j++) {
                sb.append((char)Math.round(Math.random() * 0xFFFF));
            }
            strings.add(sb.toString());
        }

        return strings;
    }


    public static void showMemoryUsage(String action) {
        long free = Runtime.getRuntime().freeMemory();
        long total = Runtime.getRuntime().totalMemory();
        long max = Runtime.getRuntime().maxMemory();
        long used = total - free;
        System.out.printf("%30s: %10d of max %10d%n", action, used, max);
    }
}
import java.util.ArrayList;

public class GcWithoutIterator {

    public static void main(String[] args) {
        System.gc();
        showMemoryUsage("Without iterator initial");

        final int N = 7500;
        ArrayList<String> strings = generateLargeStringsArray(N);
        showMemoryUsage("Array created");




        strings = null;
        System.gc();

        showMemoryUsage("After GC");

        ArrayList<String> strings2 = generateLargeStringsArray(N);
        showMemoryUsage("After second array creation");
    }

    private static ArrayList<String> generateLargeStringsArray(int N) {
        ArrayList<String> strings = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            StringBuilder sb = new StringBuilder(N);
            for (int j = 0; j < N; j++) {
                sb.append((char)Math.round(Math.random() * 0xFFFF));
            }
            strings.add(sb.toString());
        }

        return strings;
    }


    public static void showMemoryUsage(String action) {
        long free = Runtime.getRuntime().freeMemory();
        long total = Runtime.getRuntime().totalMemory();
        long max = Runtime.getRuntime().maxMemory();
        long used = total - free;
        System.out.printf("%30s: %10d of max %10d%n", action, used, max);
    }
}
import java.util.ArrayList;

public class IteratorAndGc {

    // number of strings and the size of every string
    static final int N = 7500;

    public static void main(String[] args) {
        System.gc();

        gcInMethod();

        System.gc();
        showMemoryUsage("GC after the method body");

        ArrayList<String> strings2 = generateLargeStringsArray(N);
        showMemoryUsage("Third allocation outside the method is always successful");
    }

    // main testable method
    public static void gcInMethod() {

        showMemoryUsage("Before first memory allocating");
        ArrayList<String> strings = generateLargeStringsArray(N);
        showMemoryUsage("After first memory allocation");


        // this is only one difference - after the iterator creating memory is not collected any more
        for (String string : strings);
        showMemoryUsage("After iteration");

        strings = null; // discard the reference to the array

        // one says this doesn't guarantee garbage collection,
        // Oracle says "the Java Virtual Machine has made a best effort to reclaim space from all discarded objects".
        // but no matter - the program behavior remains the same with or without this line. You may skip it and test.
        System.gc();

        showMemoryUsage("After force GC in the method body");

        try {
            System.out.println("Try to allocate memory in the method body again:");
            ArrayList<String> strings2 = generateLargeStringsArray(N);
            showMemoryUsage("After secondary memory allocation");
        } catch (OutOfMemoryError e) {
            showMemoryUsage("!!!! Out of memory error !!!!");
            System.out.println();
        }
    }

    // function to allocate and return a reference to a lot of memory
    private static ArrayList<String> generateLargeStringsArray(int N) {
        ArrayList<String> strings = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            StringBuilder sb = new StringBuilder(N);
            for (int j = 0; j < N; j++) {
                sb.append((char)Math.round(Math.random() * 0xFFFF));
            }
            strings.add(sb.toString());
        }

        return strings;
    }

    // helper method to display current memory status
    public static void showMemoryUsage(String action) {
        long free = Runtime.getRuntime().freeMemory();
        long total = Runtime.getRuntime().totalMemory();
        long max = Runtime.getRuntime().maxMemory();
        long used = total - free;
        System.out.printf("\t%40s: %10dk of max %10dk%n", action, used / 1024, max / 1024);
    }
}
/**
 * Run with:
 * <pre>javac IteratorAndGcSuperSimple.java && java  IteratorAndGcSuperSimple</pre>
 * (this falls to out of memory error)
 * and then
 * <pre>javac IteratorAndGcSuperSimple.java && java  IteratorAndGcSuperSimple -forceGc</pre>
 */
public class IteratorAndGcSuperSimple {

    private static final int N = (int) (Runtime.getRuntime().maxMemory() * 0.5);

    public static void main(String[] args) {
        memoryInScope();

        if (args.length > 0 && "-forceGc".equals(args[0])) {
            System.gc();
        }

        byte[] data = new byte[N];
    }

    public static void memoryInScope() {
        byte[] data = new byte[N];
        for (byte b : data) ;
    }
}

Compile and run: javac *.java && java -Xmx200m -Xms200m GcWithoutIterator && printf "\n" ; java -Xmx200m -Xms200m GcWithIterator

Output:

 Without iterator initial:    1396848 of max  201326592
                 Array created:  122590904 of max  201326592
                      After GC:     507816 of max  201326592
   After second array creation:  117085424 of max  186646528

         With iterator initial:    1396824 of max  201326592
                 Array created:  122575744 of max  201326592
                      After GC:  113337720 of max  201326592
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    at java.lang.String.<init>(String.java:207)
    at java.lang.StringBuilder.toString(StringBuilder.java:407)
    at GcWithIterator.generateLargeStringsArray(GcWithIterator.java:40)
    at GcWithIterator.main(GcWithIterator.java:29)

Comments (0)