Skip to main content

Rich-client application performance, Part 2: Plugging memory leaks

Chris Grindstaff (chris@gstaff.org), Software Engineer, IBM
Chris Grindstaff
Chris Grindstaff is a software engineer at IBM in Research Triangle Park, North Carolina. Chris wrote his first program at age 7, when he convinced his grade school teacher that "typing" sentences would be just as onerous a punishment as writing them by hand. Chris is currently interested in a variety of open source projects. He has worked extensively with Eclipse and authored several popular Eclipse plug-ins, which can be found on his Web site.

Summary:  Part 1 of this two-part article on Eclipse rich-client performance covers the basics of measuring an application's performance, applying instrumentation techniques, keeping the UI responsive, and using Jobs to avoid threading mistakes. This second part takes a look at memory usage and how to chase down memory leaks.

View more content in this series

Date:  07 Aug 2007
Level:  Intermediate
Activity:  3183 views

Part 1 of this series addressed CPU, I/O, and threading issues as some of the pieces of the performance puzzle in Eclipse-based rich-client applications. Memory leaks are another possible cause of performance problems. This article explains how to monitor your application's memory usage, describes some of the kinds of leaks you'll experience when developing rich-client applications, and shares techniques for solving them.

Understanding memory usage

Understanding a Rich Client Platform (RCP) application's total memory usage can be a tricky affair. The operating system (OS) tells you how much memory your application consumes, and the Java™ platform tells you how much heap you've consumed. The memory usage that the OS reports is always greater than the available heap size. Unfortunately, sometimes the number that the OS reports is much larger than the heap size. One of the challenges of heap analysis is determining what lies in this "dark space."

In general: process memory usage = Java heap + complied native code + bytecodes + other / native

Unfortunately, what the JVMs tell you about their heaps varies by release and vendor. Here are some examples from a Java application I'm running: Sun's 1.6 JDK reports 32.7MB heap and the OS reports 48.6MB private bytes, leaving 16MB unaccounted for. All told, that's not so bad. In this case the compiled code and bytecodes are part of the 16MB. The same application running with the IBM® 1.5 JDK has a heap plus classloaders plus compiled code of 39MB, and the OS reports 45.8MB.

Typically, you can simplify the matter by focusing only on the Java heap. This is a sufficient approach for most Java applications and the spot where you can make the largest improvements. When that isn't the case, you should use your OS's tools to examine the native memory not covered by the Java heap.

Differential analysis

One of the most productive ways to approach memory usage is to focus on the number of objects. For example, if I'm displaying 50 mail messages in an e-mail application, how many instances of the MailMessage class would I expect? Fifty, right? What about mail details or other mail domain objects? What happens if you switch folders and now a new set of 50 mail messages is displayed? How many instances do you have now: 50 or 100?

Once you start doing this type of analysis, you'll be surprised at the number of times you have more instances than you expected. A word of caution, though: Make sure that a garbage collection occurs before you gather a heap dump because you don't want to count dead objects. Typically, I do a System.gc() before capturing a heap dump.

I'm not going to describe general heap analysis, which is a well-trodden path (see Resources). Instead, I'll describe differential analysis, a technique I've used to find application leaks.

The basic idea is straightforward:

  1. Take a heap dump.
  2. Do something with the application several times (let's say 10 times).
  3. Take another heap dump.
  4. Compare the number of application objects in each heap dump.

I then build up a set of application objects that I'm interested in tracking. As leaks are found and fixed, I add the list of classes that were leaking to the script. This way, over time I build up a set of application objects that I'm always checking.

Unit testing

Another technique I've used is writing unit tests that parse the heap dump and assert the expected number of instances of the domain objects. For example, you'd start the application, run through a scenario, take a heap dump, and then do the assertions. Here's an example: A memory leak was discovered in the mail application. Once the leak was fixed, I wanted to make sure the problem wasn't reintroduced in later code changes, so I built a unit test to help prevent this. It's a resource usage unit test, shown in Listing 1:


Listing 1. JUnit test case parsing heap dumps
                
public void testOpenTenMessages() throws Exception {
Heap heap = Heap.from("openMessages.phd");
assertEquals(10, heap.instancesOf("cbg/mail/ui/message/MessageController"));
assertEquals(10, heap.instancesOf("cbg/mail/ui/message/viewer/AttachmentModel"));

Heap heapAfter = Heap.from("openMessagesClosed.phd");
assertEquals(0, heapAfter.instancesOf("cbg/mail/ui/message/MessageController"));
assertEquals(0, heapAfter.instancesOf("cbg/mail/ui/message/viewer/AttachmentModel"));
}

Here's how it works: 10 mail messages are opened and a heap dump named openMessages.phd is created. Then the messages are closed and a second heap dump, named openMessagesClosed.phd, is taken.

With these two heap dumps, I now write assertions about the various domain objects I expect to be in memory. I expect there to be 10 mail messages (MessageControllers) in the first heap dump and none in the second.

This style of automated heap analysis is a powerful way to track build-to-build changes. As with standard unit tests, you can ease your way into it, initially creating them only when you find and fix leaks. That way, you can ensure that you don't regress. It's useful to think of your application's resource usage as another metric that should be tracked. Even knowing how many objects your application has allocated at the end of a run is useful from build to build.

Unfortunately, heap analysis is one of those areas in which JVMs (and different versions of the same JVM) differ the most. The IBM JVMs have changed the heap-analysis format a few different times. Sun's JVM uses a different format and has changed between releases too.


Leaking graphical device interface resources

On the Windows® OS, each color, font, graphics context (GC), image, cursor, or region corresponds to a single graphical device interface (GDI) resource. GDI is a Windows term, but every OS has an equivalent. The important thing to remember is that the entire OS has a limited number of GDI resources. If your application leaks or uses too many resources, you'll impact all applications running on the system. GDI leaks are bad.

It's simple to determine if GDI resources are leaking. On the Windows OS, you can use Task Manager or Process Explorer. Add the GDI column and see if it grows over time (see Figure 1). For example, you may notice that each time you open a mail message, the number of GDI resources associated with the javaw process increases by 50, but when you close the mail message, the number of GDI resources decreases by only 46. Each time you read a mail message, you leak four GDI resources.


Figure 1. Windows Task Manager showing GDI objects
Windows Task Manager showing GDI objects

Although Task Manager can tell you when you're leaking, it can't help you find where you're leaking. The best way to do that is with Sleak, a SWT development tool (see Resources). SWT has debug flags you can enable that cause it to track where each GDI resource is created. Sleak lets you see the GDI resources and where they've been allocated from.

SWT and JFace provide several different classes to help manage your GDI resources in various caches. Caching is often trickier than you might think. It's not always obvious how or when to use caches. Some of the design tensions are:

  • GDI leaks are always unacceptable. They must be fixed.
  • After leaks are fixed, you should consider two other issues:
    • Total number of GDI resources required by your application.
    • The cost of creating the resources.

Total number of GDI resources

You need to be aware of the total number of GDI resources in your application and how many of those resources are duplicates. Duplicates are important because you should share GDI resources whenever possible to reduce the total that your application uses. It's easy to create duplicates without realizing it. (I've modified the Sleak tool to find duplicates and am contributing this and other useful Sleak changes back to Eclipse.)

Cost of creating GDI resources

Typically fonts and images are more expensive to create than colors. Depending on the application, image creation can be a significant cost of some user actions. When that's the case, you may want to consider some of the SWT/JFace-provided caches.

When possible, let the platform manage the resources. When you specify the image or icon attribute in platform extensions -- such as views, actions, and so on -- the platform is responsible for making sure the resources are created and disposed correctly. The best code is often the code you don't need to write and maintain. A corollary of this tip is to increase sharing by using the existing platform's fonts and images when possible.

Share your resources whenever possible. This is usually best achieved by grouping the resources in a common bundle. Think of this as refactoring your common resources.

Each UI bundle has an ImageRegistry associated with it. This registry can be used to store frequently used images. Frequently used is the key here. I've seen developers put all their resources in the registry, which isn't typically appropriate. The registry maintains a mapping of name > image or name > image descriptor. Image descriptors are lightweight descriptions of an image; they don't have any GDI associated with them. You can prepopulate your image registry with the image descriptors, and the first time the image is requested, the registry will create it for you.

For less frequently used images, you can create or dispose them yourself or use a LocalResourceManager. One form of the LocalResourceManager's constructor takes a widget. When created that way, the LocalResourceManager cleans up the resources associated with it when the widget is disposed.


Listener leaking

Leaks associated with listeners are common problems with UI code (see Resources). Listener leaks often waste memory and time. When you add a listener to an object, you're creating a direct, strong association between the widget and your listener (see Figure 2). Your listener, and everything it references, is retained in memory as long as the widget is. When the widget, or its containing parent, is disposed, SWT removes the listeners, thereby breaking the strong association. I've seen a lot of code that shows developers are often confused on this issue.


Figure 2. Listener example
Listener example

You don't need to remove JFace/SWT listeners, as long as the object you're adding them to is disposed in a timely fashion. Understanding the life cycle of the object that you're adding listeners to is key. Whenever you add a listener to an object, you need to ask yourself what the listener's lifetime is and to which object you're adding the listener.

For example, suppose your application creates a view. The view contains a button. As you're building the view, you add a selection listener to the button so that your application can respond to button clicks. You don't need to add a dispose listener to the view that removes the button listener, and you also don't need to add a dispose listener to the button that removes the button listener when the button is disposed. SWT takes care of removing the button's listener when the button is disposed. You needn't write the redundant code and manage the extra work.

A common example in RCP applications happens when someone creates a view part that adds itself as a workbench page listener. Workbench pages tend to be long-lived. Often a workbench page isn't closed (which would clean up the listener) until the application is shut down. In a situation like this, you shouldn't rely on the workbench page to clean up the listener relationship. You should remove the view part as a listener when the view part is closed.

I saw another example of confusion over object life cycle in a chat program. Each time a chat window was opened, it would add a listener to the buddy list. The chat window never removed the listener, which wouldn't have been a problem except that the buddy list was never disposed either. The end result was that more and more listeners were added to the buddy list and never removed. It's important to highlight that this was a memory leak as well as a performance hit. Each chat window and all reachable objects would be retained as a result of this listener leak. Also, each time the buddy list would signal its list of listeners, it would waste time notifying the chat windows that had previously been closed.

Another common example is adding listeners to a preference store so you can update your UI when a preference changes. I've seen developers add a preference store listener when their view is created or when an action is created. The problem is that if you don't remove the preference store listener, you'll accumulate listeners because the preference store is typically closed only at shutdown.

Actions are a special case. Actions don't really have a life cycle. They are created, but you don't really have much control over when they're disposed or no longer being used. This means that when you're creating an action, you typically shouldn't add listeners to other objects because you don't have a well-defined way to remove those listeners.

How to find listener leaks

I recommend two approaches for finding listener leaks:

  • Perform code review: I look for places where the application code is adding listeners to objects that I think will live longer than anticipated. I make a list of these and then, typically using the debugger at run time, I validate these assumptions. The fact that a matching removeListener exists for each addListener isn't sufficient because developers often make the mistake of including a removeListener in a method they think will be called, but in fact isn't.

  • Use a profiler or differential analysis, along the lines of:
    1. Start application.
    2. Warm up.
    3. Take a memory snapshot.
    4. Do action (open chat window, read mail message, and so on) five times.
    5. Take a memory snapshot.
    6. Look at the number of instances of application objects. When you have a listener leak, for example, you'll see five more listeners than there should be.

Conclusion

I hope this article has given you some ideas on how to measure your application's heap usage from build to build, plus a few handy techniques for finding and fixing leaks when they inevitably occur. If you aren't already doing so, try tracking the amount your application uses from build to build. Once you've done that, try adding heap analysis. Even if you don't do much with the heap dumps at first, collecting them from build to build is invaluable when you do need to make use of them. Once you start collecting the heap dumps, you can include differential analysis for your domain objects. Start small and add more as you grow familiar with these techniques.


Resources

Learn

Get products and technologies

Discuss

About the author

Chris Grindstaff

Chris Grindstaff is a software engineer at IBM in Research Triangle Park, North Carolina. Chris wrote his first program at age 7, when he convinced his grade school teacher that "typing" sentences would be just as onerous a punishment as writing them by hand. Chris is currently interested in a variety of open source projects. He has worked extensively with Eclipse and authored several popular Eclipse plug-ins, which can be found on his Web site.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=245498
ArticleTitle=Rich-client application performance, Part 2: Plugging memory leaks
publish-date=08072007
author1-email=chris@gstaff.org
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers