Written by Brian Duff, Oracle Corporation
January 2004
Memory leaks in Java applications can sometimes be tricky to detect and diagnose.
Although garbage collection in Java eliminates much of the fuss of manual deallocation
needed in languages like C, it's sometimes a double-edged sword. Cleaning up
is mandatory (if somewhat tedious) in languages like C, but Java developers
are accustomed to just letting the garbage collector take care of things. Although
objects don't need to be explicitly deallocated in Java, it's easy to accidentally
overlook references. Forgetting to remove a reference from an active object
to one you're no longer interested in using will cause the latter object to
remain pinned in memory, and the garbage collector will not free up the space
it's using on the heap.
JDeveloper's debugger can be enormously useful in tracking down memory leaks.
This tip describes a specific memory leak I encountered in JDeveloper's own
code prior to the 10g preview release, and how JDeveloper's debugger came to
my aid to find those stray references.
In JDeveloper, you can switch between version control systems while the IDE
is running. For example, if you're using Oracle SCM, you can switch to CVS and
back again without having to restart JDeveloper. I wanted to be sure that when
you make a switch, the bulk of the implementation of the previous system was
made eligible for garbage collection and not hogging resources unnecessarily.
Some monitoring of JDeveloper's memory usage lead me to believe that this wasn't
happening.
Firstly, I configured my JDeveloper project to use OJVM remote debugging (on
the Debugger/Remote page of project properties). I then started
a second copy of JDeveloper in debug mode. If you're developing an extension
for JDeveloper, the easiest way to remote debug JDeveloper is using the -debug
command line option to jdev.exe. This option is really just a shortcut
for the "proper" way to start OJVM remote debugging, passing in -ojvm
-XXdebugport4000 as arguments to java. After doing this,
JDeveloper (or the java process) will start and then immediately suspend waiting
for the debugger to connect; you can now use the Debug menu
to connect and begin debugging.
With the remote debugger running, I paused execution while Oracle SCM was the
active version control system. Bringing up the Classes window (View->Debugger->Classes),
I saw that there were a number of instances of implementation classes from the
Oracle SCM system in memory, as expected:
I then resumed execution of the debugee, switched from Oracle SCM to CVS in
the debugee, and paused execution again in the debugger. The JDeveloper toolbar
has a useful garbage collection toolbar button which can be used to request
that the garbage collection take place on the debuggee. After using this, and
inspecting the classes window again, I was surprised to see an instance of RSCStatusCache
still hanging around. As a cache, this object could potentially be consuming
a lot of memory. More to the point, its presence on the heap while CVS is the
active version control system was simply wasteful, because it's not being used
at all by the CVS extension.
So the next task was to find out what was actually pinning this beastie on
the heap and preventing the garbage collector from doing its job. That information
was available on the Heap window. The easiest way to view this is to right click
on a class in the Classes window you're interested in and choose the Display
in Heap context menu.
The heap window shows two types of folder: class folders and reference
path folders. Class folders contain a list of all instances of a class
that are currently on the heap. In the image above, @1E5AF48C is an object reference
for an instance of RSCStatusCache. This only told me that my object was pinned;
the question of what's doing the pinning had not yet been resolved. I needed
to find out what was referring to this object, and that information is provided
by a reference path folder. I used the Show Reference Paths
context menu on @1E5AF48C to add a reference path folder for the object. Expanding
that showed reference paths to the object which are expanded to find the actual
direct reference using the Expand Reference Path context menu
item. The field shown in bold (_cache) is directly responsible
for pinning this object.
This information was enough to solve the mystery of the leak. An anonymous
inner class (IconOverlayTracker$3) was listening to events on the singleton
instance of oracle.ide.Ide. Anonymous inner classes have an implicit reference
to their owning class (this$0), and this reference was keeping VCSDefaultUtils.NodeOverlayTracker
on the heap, and preventing my status cache from being garbage collected. Simply
changing the code to remove the listener when the overlay tracker is unregistered
solved the problem... No more leak :)