Garbage collection in Java while references still exist

Posted by – July 24, 2011

Who would have thought it, my first meaningful post is on garbage collection!

There’s a common misconception that objects can only be garbage-collected when there no longer exist any references to those objects. Actually, HotSpot goes one better, and garbage collects when it determines you’re not going to use those references again. Which might be in the middle of a method while your variables are still theoretically in scope.

Here’s how I know this? I’m unable to concentrate long enough to read any actual documentation on the garbage collector, but I found some surprising results from this experiment, which simply attemepts to add 17 million Foos to a list 4 times, catches out of memory errors, and prints its results:

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

public class Foo {

    public static void main(String[] args) {
        test1();
        test2();
    }

    static void test1() {
        List<Foo> a = new ArrayList<Foo>();
        try {
            for (int i = 0; i < 17000000; i++) {
                a.add(new Foo());
            }
        } catch (OutOfMemoryError e) {
            System.out.println("a: out of memory");
        }
        System.out.println("Created " + a.size() + " foos");
        
        List<Foo> b = new ArrayList<Foo>();
        try {
            for (int i = 0; i < 17000000; i++) {
                b.add(new Foo());
            }
        } catch (OutOfMemoryError e) {
            System.out.println("b: out of memory");
        }
        System.out.println("Created " + b.size() + " foos");
    }

    static void test2() {
        List<Foo> a = new ArrayList<Foo>();
        try {
            for (int i = 0; i < 17000000; i++) {
                a.add(new Foo());
            }
        } catch (OutOfMemoryError e) {
            System.out.println("a: out of memory");
        }
        System.out.println("Created " + a.size() + " foos");
        
        List<Foo> b = new ArrayList<Foo>();
        try {
            for (int i = 0; i < 17000000; i++) {
                b.add(new Foo());
            }
        } catch (OutOfMemoryError e) {
            System.out.println("b: out of memory");
        }
        System.out.println("Created " + b.size() + " foos");
        a.size(); // <-- IMPORTANT: REFERENCE TO a
    }
}

In test1() we get the results
Created 17000000 foos
Created 17000000 foos

indicating that there was sufficient memory to create a second list of 17000000 Foos.
In test2() we get
Created 17000000 foos
b: out of memory
Created 3392918 foos

The only difference is that in test2(), we needed to keep hold of List a, in order to evaluate a.size() at the end. And we ran out of memory, which indicates the memory must have been freed during the method in test1().

Leave a Reply

Your email address will not be published. Required fields are marked *