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<String> strings = new HashSet<String>() {{   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<String>> 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<String> createStringsInnerClass() {
            Set<String> strings = new HashSet<String>() {{
                add("a"); add("b");
            }};
            return strings;
        }

        public Set<String> createStringsNormally() {
            Set<String> strings = new HashSet<String>();
            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.

Advertisements

Leave a comment

Filed under Uncategorized

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s