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.
java.lang.OutOfMemoryError
Isn’t this a cute little error:
Exception java.lang.OutOfMemoryError: requested 4096000 bytes for GrET* in C:/BUILD_AREA/jdk1.5.0_09/hotspot\src\share\vm\utilities\growableArray.cpp. Out of swap space?
I got home from Shanghai to see this guy, although I’m not sure why a request for 4MB of memory would fail…
Update: I wasn’t leaking memory or anything, either. And I gave a big heap to play with. Maybe the system as a whole ran out of some kind of addressable memory, say threads or process space, and it threw this error. I don’t honestly know, since this was on Windows XP 32bit Pro, and not linux. I neglected at the time to save a coredump, as well.
Name clash: The method BLAH has the same erasure as type BLAH but does not override it
I was getting the following error in Eclipse IDE 3.1 and Java 1.5 (or 5.0 as some like to call it):
Name clash: The method removeEldestEntry(Map.Entry<K ,V>) of type LRUMap<K ,V> has the
same erasure as removeEldestEntry(Map.Entry<K , V>) of type LinkedHashMap<K , V> but does not
override it
The class in question looked like this:
class LRUMap <K , V> extends LinkedHashMap {
public LRUMap(){
super(10000, .75f, true);
}
protected boolean removeEldestEntry (Entry <K , V> eldest) {
return this.size() > 262144;
}
}
The problem is that I was extending LinkedHashMap without type parameters, not LinkedHashMap >K ,V<. Changing the code to:
class LRUMap <K , V> extends LinkedHashMap <K , V> {
public LRUMap(){
super(10000, .75f, true);
}
protected boolean removeEldestEntry (Entry <K , V> eldest) {
return this.size() > 262144;
}
}
completely fixed the problem! Type erasure is sure a pain, no? I should probably spend more time at home reading the Java Generics Tutorial.