Monday, June 2, 2008

Memory Leak

In computer science, a memory leak is a particular type of unintentional memory consumption by a computer program where the program fails to release memory when no longer needed. This condition is normally the result of a bug in a program that prevents it from freeing up memory that it no longer needs.

In Java, while the GC does a great job at removing unreachable objects, it doesn’t help against memory leaks as they might occur by sloppy code which leaves references to unused objects. In other words, when an object's life is longer than expected, memory leak happens. Memory leak is not a flaw of JVM or GC. It is caused by the codes itself.
The following list contains the common trouble-makers and some solutions:
  • Objects defined in a higher scope than they should might stay alive longer than expected. Always define objects in the lowest scope possible for them.
  • Listeners for observable objects which were not removed after their task was done will stay alive, receive events and generally spend processor and memory resources for no good reason. Always make sure that listeners are removed from their observable when they’re not needed anymore.
  • Exceptions might change the control flow, which might skip the code you wrote which removes those pesky listeners. Always use the finally clause when removing references to listeners or other type of objects from usually persistent collections.
  • Instances of inner classes contain an implicit reference to their outer class. You must be aware of this behavior, and if you don’t use the outer class, define the inner class as static. Suppose you implement something like a cache. Next follow the execution path to a local object which stores some of its inner class objects into the cache. After the local object was out of scope, it won't be garbage collected anymore! The inner class object in the cache holds a reference to the surrounding object and that one is still referenceable and therefore not a candidate for garbage collection anymore. The same is true for anonymous classes! Refer to http://blogs.oracle.com/olaf/2007/09/18.
  • Sometimes keeping additional information is required for certain types, but the class cannot be extended or it will prove a bad design to do so. For these cases, a Map instance is usually used to map between the object and its extended metadata. The kept objects usually should remove themselves from the map when their use is over, which is often forgotten. Luckily, WeakHashMap keeps the keys as weak references and it should be used for such metadata. Listing 1 shows a program that has a memory leak. MapLeaker processes tasks in a thread pool and records the status of each task in a Map. Unfortunately, it never removes the entry when the task is finished, so the status entries and the task objects (along with their internal state) accumulate forever.
    Listing 1. Program with a Map-based memory leak

    public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map taskStatus = Collections.synchronizedMap(new HashMap());
    private Random random = new Random();
    
    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };
    
    private class Task implements Runnable {
    private int[] numbers = new int[random.nextInt(200)];
    
    public void run() {
    int[] temp = new int[random.nextInt(10000)];
    taskStatus.put(this, TaskStatus.STARTED);
    doSomeWork();
    taskStatus.put(this, TaskStatus.FINISHED);
    }
    }
    
    public Task newTask() {
    Task t = new Task();
    taskStatus.put(t, TaskStatus.NOT_STARTED);
    exec.execute(t);
    return t;
    }
    }
    Refer to http://www.ibm.com/developerworks/java/library/j-jtp11225/index.html
  • And obviously the use of the finalize method which might be extremely slow and delay the claiming of new memory spaces, or even do worse and resurrect the finalized object!
  • Throwing any kind of Exception or Error from finalize() also prevents the garbage collector from reclaiming the memory

Here is the code to test memory leaks.
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!
Refer to http://elliottback.com/wp/archives/2008/02/03/java-memory-leaks-w-finalize-examples/

No comments:

Post a Comment