Back

Java’s Anonymous Inner Classes: Proceed With Caution

Java’s anonymous inner classes can be useful, but you must take care when using them. In particular: A memory leak will occur if an instance of an inner class survives longer than its containing outer class because the inner class maintains a reference to its outer class.

This is not a merely academic concern, this comes up in Android development when we use inner classes to encapsulate and pass around some code, and instances of an outer class (Activities) can be constantly created and destroyed.

Concrete Example

Sometimes we want a quick Set with some elements, and can accomplish this with a one-liner:

Set strings = new HashSet() {{   add("a"); add("b");  }};

Admittedly there are better ways to accomplish this, but this construct is possible and is used from time to time. What is not obvious is that this is actually an instantiation of an anonymous inner class that maintains a reference to its enclosing class, and so is subject to the potential memory leak situation described above.

In full context, here is some code that may look innocent enough (contrived though it may be)


public class Application {

    public static void main(String[] args) throws IOException {

        List<Set> stringList = new ArrayList<>();
        for(int i=0; i < 100; i++) {
            Generator gen = new Generator();
            stringList.add(gen.createStringsInnerClass());
        }
        
        System.out.print("press Enter to continue:");
        System.in.read();
    }

    public static class Generator {
        
        private byte[] lotsOfHiddenStuff = new byte[5_000_000];
        
        public Set createStringsInnerClass() {
            Set strings = new HashSet() {{
                add("a"); add("b");
            }};
            return strings;
        }

        public Set createStringsNormally() {
            Set strings = new HashSet();
            strings.add("a");
            strings.add("b");
            return strings;
        }
    }
}

Analyze This

If we run the above application and monitor it with jconsole, this is what we see:

inner class after gc

It is using over 500MB of memory! This is because the Sets maintain references to their outer classes (which themselves take up a lot of memory) and it is not obvious that the outer class instances are not being properly disposed of.

After switching to use Generator.createStringsNormally(), jconsole looks like this:

non inner class after gc

It is using just over 5MB, as we would hope!

Conclusion

Remember that a memory leak will occur if an instance of an inner class survives longer than its containing outer class. And maybe avoid the “{{ }}” construct because it kind of masks the anonymous-inner-class-ness of your code.

Read more