When you
switch from a language with manual memory management, such as C or C++, to a garbage-collected
language, your job as a programmer is made much easier by the fact that your
objects are automatically reclaimed when you’re through with them. It can
easily lead to the impression that you don’t have to think about memory
management, but this isn’t quite true.
Consider the
following simple stack implementation:
// Can you spot the "memory leak"?
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to
grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
Loosely
speaking, the program has a “memory leak,” which can silently manifest itself
as reduced performance due to increased garbage collector activity or increased
memory footprint. In extreme cases, such memory leaks can cause disk paging and
even program failure with an
OutOfMemoryError, but such failures are relatively rare.
So where is
the memory leak? If a stack grows and then shrinks, the objects that were
popped off the stack will not be garbage collected, even if the program using
the stack has no more references to them. This is because the stack maintains obsolete references to these objects. An obsolete reference is
simply a reference that will never be dereferenced again.
The fix for
this sort of problem is simple: null out references once they become obsolete.
In the case of our Stack class, the reference
to an item becomes obsolete as soon as it’s popped off the stack. The corrected
version of the pop method looks like this:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete
reference
return result;
}
Nulling out
object references should be the exception rather than the norm.
Generally
speaking, whenever a class manages its own memory, the programmer should be
alert for memory leaks. Whenever an element is freed, any object
references contained in the element should be nulled out.
Another common
source of memory leaks is caches. Once you put an object reference into a
cache, it’s easy to forget that it’s there and leave it in the cache long after
it becomes irrelevant.
A third common
source of memory leaks is listeners and other callbacks. If you
implement an API where clients register callbacks but don’t deregister them
explicitly, they will accumulate unless you take some action.
They are
typically discovered only as a result of careful code inspection or with the
aid of a debugging tool known as a heap profiler. Therefore,
it is very desirable to learn to anticipate problems like this before they
occur and prevent them from happening.
Reference: Effective Java 2nd Edition by Joshua Bloch