Java Memory Leaks w/ Finalize Examples
Most people think Java cannot leak memory, but actually that’s not true. Java, unlike languages such as C or Javascript, can have two kinds of memory leaks:
- True leaks – unreferenced, unrecoverable segments of memory
- Soft leaks – memory that could be garbage collected, but is “accidentally” referenced
The first kind of memory leak, the true leak, is common to C. It’s trivially easy to write a C program which leaks memory like a sieve by simple putting a call to malloc(…) inside a tight loop. This creates unbounded amounts of heap memory and eventually runs out of space. Additionally, the memory is lost if you don’t save a pointer to it. However, the same program in Java will not run out of memory:
public class finalizer {
public static void main(String[] args) {
while (true) {
finalizer f = new finalizer();
System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
}
}
}
The output looks similar to this:
15757280 bytes free!
15965176 bytes free!
16274928 bytes free!
16584368 bytes free!
15770768 bytes free!
…
As you can see, since the object we create are unreferenced, the garbage collector can clean them up. Circular references are also no problem for the Java garbage collector, which uses a generational mark-and-sweep algorithm, as demonstrated by this example:
import java.util.LinkedList;
import java.util.List;
public class finalizer {
protected Object ref;
public finalizer(Object ref){
this.ref = ref;
}
public static void main(String[] args) {
while (true) {
List x = new LinkedList();
for (int i = 0; i < 100000; i++) {
x.add(new finalizer(x));
}
x = null;
System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
}
}
}
This creates a linked list in which every element also contains a reference to the list, then blows away the scope of the list entirely by dereferencing x and moving into a new loop scope. This leaves all the elements we just allocated floating in memory and all pointing at each other, but not pointed to by our active memory graph. The output shows that this memory is reclaimed:
12601488 bytes free!
8597784 bytes free!
4584656 bytes free!
12596808 bytes free!
…
Unfortunately, though, there is a way to create true memory leaks in java, and that is with a poor implementation of the finalize method, which is described in the Java 6 docs as:
The general contract of finalize is that it is invoked if and when the JavaTM virtual machine has determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, except as a result of an action taken by the finalization of some other object or class which is ready to be finalized.
The finalize method exists on every Object and is called by the garbage collector’s thread before it reclaims that memory. So, the simplest thing we can do to prevent the garbage collector from reclaiming it is to yield execution:
public class finalizer {
@Override
protected void finalize() throws Throwable {
while (true) {
Thread.yield();
}
}
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 100000; i++) {
finalizer f = new finalizer();
}
System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
}
}
}
This produces a quick memory leak and Out of Memory Exception:
12591736 bytes free!
8599816 bytes free!
4584576 bytes free!
602496 bytes free!
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
Throwing any kind of Exception or Error from finalize() also prevents the garbage collector from reclaiming the memory:
public class finalizer {
@Override
protected void finalize() throws Throwable {
throw new Exception("x");
}
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 100000; i++)
new finalizer();
System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
}
}
}
12380920 bytes free!
8687296 bytes free!
5608880 bytes free!
1796496 bytes free!
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
Abuse of the Object.finalize(…) contract is the only way I know of in pure Java to create a true memory leak. If you widen the scope to include JNI (Java Native Interface), you can bind to an object which leaks memory in native code, but inside the process space of your Java application.