Beware garbage collection when working with inner classes Credit: FILO / Aleksei Derin / Getty Images If you’ve read my Java 101 tutorial introducing static classes and inner classes, you should be familiar with the basics of working with nested classes in Java code. In this associated tip, I’ll walk you through one of the pitfalls of nesting classes, which is the inner class’s potential for causing a memory leak and out-of-memory error in the JVM. This type of memory leak occurs because an inner class must at all times be able to access its outer class–which doesn’t always work with the JVM’s plans. Getting from a simple nesting prodedure to an out-of-memory error (and possibly shutting down the JVM) is a process. The best way to understand it is by watching it unfold. Step 1: An inner class references its outer class Any instance of an inner class contains an implicit reference to its outer class. For example, consider the following declaration of EnclosingClass with its nested EnclosedClass non-static member class: public class EnclosingClass { public class EnclosedClass { } } To better understand this connection, we can compile the above source code (javac EnclosingClass.java) into EnclosingClass.class and EnclosingClass$EnclosedClass.class, then examine the latter class file. The JDK contains a javap (Java Print) tool for disassembling class files. On the command line, follow javap with EnclosingClass$EnclosedClass, as follows: javap EnclosingClass$EnclosedClass You should observe the following output, which reveals a synthetic (manufactured) final EnclosingClass this$0 field that holds a reference to EnclosingClass: Compiled from "EnclosingClass.java" public class EnclosingClass$EnclosedClass { final EnclosingClass this$0; public EnclosingClass$EnclosedClass(EnclosingClass); } Step 2: The constructor captures the enclosing class reference The above output reveals a constructor with an EnclosingClass parameter. Execute javap with the -v (verbose) option and you’ll observe the constructor saving an EnclosingClass object reference in the this$0 field: final EnclosingClass this$0; descriptor: LEnclosingClass; flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC public EnclosingClass$EnclosedClass(EnclosingClass); descriptor: (LEnclosingClass;)V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LEnclosingClass; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return LineNumberTable: line 3: 0 Step 3: Declare a new method Next, suppose you declare a method in another class that instantiates EnclosingClass, followed by EnclosedClass. The next code fragment reveals this instantiation sequence: EnclosingClass ec = new EnclosingClass(); ec.new EnclosedClass(); The javap output below shows the bytecode translation for this source code. Line 18 reveals the call to EnclosingClass$EnclosedClass(EnclosingClass). This call is to save the enclosing class reference in the enclosed class: 0: new #2 // class EnclosingClass 3: dup 4: invokespecial #3 // Method EnclosingClass."<init>":()V 7: astore_1 8: new #4 // class EnclosingClass$EnclosedClass 11: dup 12: aload_1 13: dup 14: invokestatic #5 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object; 17: pop 18: invokespecial #6 // Method EnclosingClass$EnclosedClass."<init>":(LEnclosingClass;)V 21: pop 22: return Anatomy of a memory leak In the above examples, we’ve stored a reference of an enclosing class in a manufactured variable of the enclosed class. This can lead to a memory leak in which the enclosing class references a large graph of objects that cannot be garbage collected. Depending on the application code, it’s possible to exhaust memory and receive an out-of-memory error, resulting in termination of the JVM. The listing below demonstrates this scenario. Listing 1. MemoryLeak.java import java.util.ArrayList; class EnclosingClass { private int[] data; public EnclosingClass(int size) { data = new int[size]; } class EnclosedClass { } EnclosedClass getEnclosedClassObject() { return new EnclosedClass(); } } public class MemoryLeak { public static void main(String[] args) { ArrayList al = new ArrayList<>(); int counter = 0; while (true) { al.add(new EnclosingClass(100000).getEnclosedClassObject()); System.out.println(counter++); } } } The EnclosingClass declares a private data field that references an array of integers. The array’s size is passed to this class’s constructor and the array is instantiated. The EnclosingClass also declares EnclosedClass, a nested non-static member class, and a method that instantiates EnclosedClass, returning this instance. MemoryLeak‘s main() method first creates a java.util.ArrayList to store EnclosingClass.EnclosedClass objects. Ignore the use of packages and generics for now, along with ArrayList (which stores objects in a dynamic array)–the important point is to observe how the memory leak occurs. After initializing a counter to 0, main() enters an infinite while loop that repeatedly instantiates EnclosedClass and adds it to the array list. It then prints (or increments) the counter. Before the enclosed class can be instantiated, EnclosingClass must be instantiated, with 100000 being passed as the array size. Each stored EnclosedClass object maintains a reference to its enclosing object, which references an array of 100,000 32-bit integers (or 400,000 bytes). This outer object cannot be garbage collected until the inner object is garbage collected. Eventually, this application will exhaust memory. Compile Listing 1 as follows: javac MemoryLeak.java Run the application as follows: java MemoryLeak I observe the following suffix of the output–note that you might observe a different final counter value: 7639 7640 7641 7642 7643 7644 7645 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at EnclosingClass.<init>(MemoryLeak.java:9) at MemoryLeak.main(MemoryLeak.java:30) OutOfMemoryError is an example of a Java exception. See Exceptions in Java, Part 1 for more about throwing and handling Java exceptions in your programs. Related content news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages analysis And the #1 Python IDE is . . . PyCharm, VS Code, and five other popular Python IDEs duke it out. Which one do you think takes home the prize? By Serdar Yegulalp Nov 15, 2024 2 mins Python Programming Languages Software Development news JDK 24: The new features in Java 24 21 features are proposed for the next version of Java including quantum-resistant cryptographic keys designed to secure Java apps against future quantum computing attacks. By Paul Krill Nov 15, 2024 11 mins Java Programming Languages Software Development news Rust Foundation moves forward on C++ and Rust interoperability Problem statement released to address the challenges to making cross-language development with C++ and Rust more accessible and approachable. By Paul Krill Nov 14, 2024 2 mins C++ Rust Programming Languages Resources Videos