The Source for Java Technology Collaboration
User: Password:



Tim Boudreau

Tim Boudreau's Blog

Writing Memory Leak Regression Tests with INSANE

Posted by timboudreau on April 17, 2005 at 11:49 AM | Comments (6)

A couple of years ago my friend Petr Nejedly created a handy library he calls INSANE. The reason for the name was, whenever he described the idea to people, they'd say "that's insane!" (but it is now also a cute acronym). What it does is static heap analysis - that is, it writes out every object in the JVM and how they reference each other. INSANE is also useful to diagnose OutOfMemoryErrors - just allocate some large array on application start (so you can clear enough room for INSANE to work after an OOME has happened), and install a handler for OutOfMemoryErrors which will clear the array and then dump the object graph of the JVM to a file. From there, grep is a handy tool to track down what's going on.

Download the source + library for this example here. Sources and more info on INSANE can be found here.

In NetBeans, we use INSANE for analyzing memory leaks; we also use it in our regression tests. Once you know you have a memory leak, you can use INSANE to print out exactly the reference graph that is causing the leak. So, when you fix a memory leak, you can write a unit test that will fail if the leak ever reappears. Some profilers can do this sort of thing too, but it's really nice to have a regression test that will tell you exactly what's going on without having to go in and profile the app.

A few people I've talked to about this here in Brazil have said "You mean you can have a memory leak in Java?" So we'll cover this quickly: A memory leak in Java happens when you allocate some object, and your code is now done with it, but it is still referenced somewhere, so the object can't be garbage collected - it sits around forever taking up space. Eventually you can run out of memory (or at least cause swapping and thrashing) because memory is full of objects nothing will ever use again. The most typical pattern for creating such leaks is adding something to a Collection somewhere and forgetting about it.

I've put together an example with a memory leak. First, read this code and see if you can find the leak:

public class MyFrame extends javax.swing.JFrame {
    MenuAction menuAction = new MenuAction();
    public MyFrame() {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setBounds (20, 20, 300, 300);
        getContentPane().addMouseListener (new MouseAdapter() {
            public void mouseReleased (MouseEvent me) {
                getPopupMenu().show((Component) me.getSource(), me.getX(), me.getY());
            }
        });
    }
    
    JPopupMenu getPopupMenu() {
        JPopupMenu menu = new JPopupMenu();
        menu.add (new JMenuItem (menuAction));
        return menu;
    }
    
    static final class MenuAction extends AbstractAction {
        public MenuAction() {
            putValue (Action.NAME, "Do Something");
        }
        public void actionPerformed (ActionEvent ae) {
            System.out.println("Action performed");
        }
    }
}

The above code contains a memory leak, but it's quite subtle. So lets assume we know that what is leaking is the JPopupMenu - we create a new one each time, and somehow they're not being garbage collected. So, what we'll do is write a regression test that fails - then we'll know when the bug is fixed. I've created a parent class for my unit test, called NbTestCase (get the original source which also contains assertions for data structure sizes here) - it's actually a stripped down version of a class with the same name which is part of XTest. It has a method assertGC() which we can use to assert that an object is garbage collected.

The usage pattern is simple: Null out any references in the test itself, after storing the object in question in a weak reference:

SomeObject o = new SomeObject();
something.doSomethingWith (o);
WeakReference ref = new WeakReference (o);
o = null;
assertGC ("Object still referenced", ref);

assertGC() forces the system to garbage collect several times, and if the object is still referenced from somewhere else, it shows what's holding on to it. The source is and INSANE lib are attached. So let's write our test:

public class MyFrameTest extends NbTestCase {
    
    public MyFrameTest(String testName) {
        super(testName);
    }

    MyFrame frame = null;
    protected void setUp() throws Exception {
        frame = new MyFrame();
        frame.setVisible (true);
        Thread.currentThread().sleep (1000);
    }

    public void testLeak() throws Exception {
        System.out.println("testLeak");
        JPopupMenu popup = frame.getPopupMenu();
        popup.show (frame, 0, 0);
        Thread.currentThread().sleep (1000);
        popup.setVisible (false);
        
        WeakReference ref = new WeakReference (popup);
        popup = null;
        assertGC ("Popup menu should have been collected", ref);
    }
    
    public static Test suite() {
        TestSuite suite = new TestSuite(MyFrameTest.class);
        
        return suite;
    }
}

This is where it becomes clear just how useful INSANE is - it gives us an actual reference graph, so we know precisely where the leak occured:

Testcase: testLeak(javaapplication7.MyFrameTest):	FAILED
Popup menu should have been collected:
private static java.awt.Component java.awt.KeyboardFocusManager.focusOwner->
javaapplication7.MyFrame@d1e89e->
javaapplication7.MyFrame$MenuAction@f17a73->
javax.swing.event.SwingPropertyChangeSupport@3526b0->
javax.swing.event.EventListenerList@3ddcf1->
[Ljava.lang.Object;@105c1->
javax.swing.JMenuItem$1@f74864->
javax.swing.JMenuItem@110003->
javax.swing.JPopupMenu@627086

So, our leak is simple: When you call new JMenuItem (someAction), the JMenuItem adds a property change listener to the action, to enable/disable itself. The listener is never removed, so each time a popup is shown, it hangs around as the parent of a JMenuItem which is held in the list property change listeners on the Action.

Now, I can think of about five ways to fix this bug, from obvious and straightforward to silly. Which way would you do it?


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Can't the JProfiler discover that ?

    Posted by: vbrabant on April 17, 2005 at 12:36 PM

  • I imagine so - but a test like this can run on an automated build server that's just constantly checking out the CVS, build, run tests, repeat. So if someone makes a change that results in the memory leak reappearing, the system will figure out who made changes since the last time the tests all passed and send them an email. No human intervention required.

    Posted by: timboudreau on April 17, 2005 at 12:55 PM

  • Ok. Now I catched the advantage.
    Thanks a lot.

    Posted by: vbrabant on April 17, 2005 at 01:16 PM

  • OK, I admit to being dense. How do you fix this?

    Posted by: dkkopp on April 28, 2005 at 08:35 AM

  • Good blog Tim. Just wanted to mention that the JPopupMenu leak you mentioned is fixed in 1.6:)

    For those interested I blogged on this as well at: http://weblogs.java.net/blog/zixle/archive/2005/11/weakreferences.html

    -Scott

    Posted by: zixle on February 21, 2006 at 03:21 PM

  • Have you applied this to distributed applications? If so, what has been your experience.

    Thanks...

    Posted by: tjpinard on May 25, 2006 at 07:37 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds