Brian Duff, Oracle Corporation
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 :)