Database connection events are typically handled by listeners that implement the javax.sql.ConnectionEventListener interface.
Although the listener pattern is in widespread use, it is error-prone and may cause memory leaks if not used properly. We will demonstrate the leak that may arise by showing how listeners may be implemented internally.
Consider a situation in which we register a listener to wait for events that can occur on a widget (see Figure 1). A listener table, reachable from the root set (the root is the reference to a live object; the root set is the set of all reachable objects to which references exist), comprises a linked-list of all possible listeners; a global event table points to the listener table. As events occur on the widget, event dispatchers walk through the listener table and invoke the event handler on the appropriate listeners. The listener table has a reference to the listener so that it can deliver an event when it arrives; this reference is shown as a dashed arrow in Figure 1.
However, if that reference is preserved, the object will never be collected, because it is reachable from the root set. In many cases, the object may consume a lot of system resources. It could be a large GUI object utilizing native system resources or a limited resource such as database connections. Although listeners are typically small objects and so may not present an immediate problem, larger objects that remain reachable (and therefore aren't collected) can affect performance and lead to system crashes due to the impact they have on memory.
In practice, registering a listener includes adding a reference from the listener to the object. Listener registration is achieved through a call like this:
object.addMyListener(new MyListener(...));
It is important to remove this reference with a matching call to
object.removeMyListener(l);
thus unregistering the listener and allowing object to be garbage-collected.
 |
| Figure 1: Design of a typical event based system.
|
A typical large-scale application with a GUI and a database back end may use dozens of different types of listeners, all of which have to be unregistered to avoid memory leaks.
Memory Profiling
While lapsed listeners may not be a problem with short-running programs, in long-running applications, continuous memory leakage over a long period of time may lead to memory exhaustion. The Memory profiling feature of Oracle JDeveloper 10g lets you collect memory statistics on long-running programs.
To illustrate the use of memory profiling to look for potential leaks, we created ListenerTest, a test program that creates object/listener pairs in a loop that repeats 3,000 times. The structure of the programs matches Figure 1: listeners are accessible from a global listener table and all listeners have a reference to the corresponding object. At the end of each iteration, there is a call to System.gc() to initialize a round of garbage collection.
Oracle JDeveloper's memory profiler is accessible from Run | Memory profile... You can configure the frequency of snapshots under Tools|Project properties|Profiles|Development|Profiler|Memory. In this case we set the update interval to 0.8 seconds resulting in 9 memory snapshots at the end of the run.
The memory profile view shows allocation and garbage collection statistics for each snapshot. One quick way to look for potential memory leaks is to sort by "Diff Alloc", the difference between the number of allocated objects of each type between the current and a previous snapshot. Large numbers in the Diff Alloc column as well as the "Diff sz" column indicate objects that have been allocated but not deleted, thus pinpointing potential leaks.
To further narrow down the problem, you can also pause the memory profiling process and double-click on the class of interest to see allocation sites for objects of the type in question. When the source code is available, you can double-click on an allocation site to jump right to the source.
| Do's and Don'ts
Do's and Don't to avoid memory leaks.
- Don't assume that the garbage collector will deallocate all unused memory for you.
- Do profile long-running programs to find memory leaks.
- When using the listener pattern, make sure that register and deregister-listener calls match on all execution paths.
|
Heap Debugging
Knowing that potentially leaking objects have been allocated is sometimes not enough, however. To get to the source of the leak, you must ask why the garbage collector is keeping the objects.
To answer that question, we can use Oracle JDeveloper 10g's heap debugging capabilities. Once you have determined which classes are potentially leaking through memory profiling, you can obtain more detailed information by using the Heap window of JDeveloper's debugger. For LapsedListenerTest, the class that appears to be leaking from the memory profiler view is the inner class, ListenerTest$Component.
We ran the test program in debug mode, right-clicked in the Heap window, and selected Add class folder to show all instances of the class we are interested in that are available at runtime. As shown in Figure 3, there are 7 instances of that class. We first looked for reference paths for a ListenerTest$Component object located at 0X4B7B4DE4. Then we right-clicked on the first of two reference paths corresponding to a static field and selected Expand reference path, which expands the tree view to show how to reach the object in question. As expected, the ListenerTest$Component object in question is reachable from the roots through the listener table at 0X48755AEC and then one of the listeners at 0X4875C1B4.
 |
| Figure 3: Using the Heap window in JDeveloper to isolate memory leaks.
|
Getting Rid of the Leaks
Once you find the memory leaks, you have a variety of ways to get rid of them. One approach is to use WeakReferences, which are not traversed by the garbage collector when determining which objects are reachable. Thus, if we make the reference from the listener list to the listener a weak one as shown below, the object of type ListenerTest$Component will eventually become unreachable and will be collected:
public static class Component {
...
public void addListener(ComponentListener listener){
synchronized (listeners){
listeners.add(new WeakReference(listener));
}
}
}
}
However, in a large system, keeping track of the listeners is done typically behind the scenes, so the developer's only way to get rid of the leak will be to call the remove method on all program paths that register the listener causing the object being listened on to eventually be collected.
Conclusion
Although Java's garbage collector may not solve all memory problems, and can leave leaks that can result in memory exhaustion, Oracle JDeveloper comes fully equipped with tools to address the variety of memory problems that might arise. In this article we have shown how to effectively use JDeveloper's memory profiler and heap debugger to locate memory errors.
Next Steps
Optmizing and Profiling next steps
1) Download Oracle JDeveloper 10g (10.1.3) Developer Preview
2) Online Help: Learn more about Oracle JDeveloper Profilers
3) Track down memory leaks Using JDeveloper's Debugger
Additional Information:
- Online Help: Learn more about the powerful Oracle JDeveloper Debugger
- Learn how to debug a multithreaded java application, using the convenient conditional breakpoints
- Oracle JDeveloper Debugger Tips
- Remote Debugging with Oracle JDeveloper
|
V. Benjamin Livshits (livshits@cs.stanford.edu)is a Ph.D. candidate at Stanford University. His interests are in designing tools for automatic error detection for systems written in C and Java. He has lately been focusing on static program analysis for finding security errors in Java applications. Benjamin received a B.S. from Cornell University in 1999 and an MS from Stanford University in 2002.
[Back to J2EE Series Home Page]