Mastering J2EE Application Development Series

   Step 11 of 12: Optimizing and Profiling

Looking for Memory Leaks
by V. Benjamin Livshits

Learn how memory profiling and heap debugging can help you get rid of memory leaks such as those caused by use of the listener pattern.

Downloads for this article
Download Article (pdf)
Benjamin's Code Samples
Oracle JDeveloper 10g

 

J2EE allows developers to create large, complex enterprise systems relatively quickly. However, applications using comprehensive, multi-layer libraries such as provided by J2EE are prone to performance bottlenecks. That's why it's especially important for developers to profile and optimize complex, multithreaded server-side applications, so that potential performance bottlenecks and resource problems, including memory leaks, can be detected and removed. Fortunately, JDeveloper comes equipped with tools that enable the profiling of J2EE applications.

Contrary to popular belief, Java's garbage collector does not solve all memory problems that can occur. In this article, we look at how Oracle JDeveloper's memory profiler feature can be used to help find leaking memory in your applications. We also show how memory leaks, once detected, can be eliminated.

Background

A key feature of memory management in Java is its garbage-collected heap. One of the typical garbage collector algorithms used by the Java Virtual Machine is the tracing collector, which traces the objects that are reachable from a set of root objects, to determine which objects should be preserved in memory — reachable objects survive garbage collection because they may conceivably be used in the course of program execution. However, if a programmer inadvertently leaves a reference to an object that in fact won't be accessed by the application, the result is unintended object retention, also known as a memory leak: the object is no longer used by the program, yet is not reclaimed by the JVM.

This type of memory leak is a common error pattern — dubbed the "lapsed listener" bug — that often arises from the mis-application of the listener pattern. The listener pattern allows classes to notify each other about events; it's a specific class of the Observer/Observable pattern, which consists of a subject and an observer. Listeners are commonly used to specify event handlers for events that occur on the object. Some commonly used event types are:

  • GUI events are typically handled by dedicated listener classes that implement the java.util.EventListener interface for GUI events in AWT, Swing, and other GUI libraries.
  • 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
    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.

    figure 1
    Figure 2: Output of the memory profiler when run on ListenerTest. Click here for larger image

    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.

    1. Don't assume that the garbage collector will deallocate all unused memory for you.
    2. Do profile long-running programs to find memory leaks.
    3. 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 1
    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]



  • Please rate this document:

    Excellent Good Average Below Average Poor


    Send us your comments

    E-mail this page
    Printer View Printer View
    Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy