For the last few weeks, I have been building a Java process monitoring
tool based on the Java Debug Interface. Although I've done much of this
work before, it has been a few years, and so now I'm retracing my
steps. As I remember the details and pitfalls, I've been posting my
notes in the hope that you'll find them useful.
Today I'm going to talk about ClassPrepareEvents, after a little background. As you probably already know, you can attach a debugger to an already-running Java process, or launch the target process itself from your debugger (using various command-line switches). In my project, I'm always going to be attaching to a running process, as the point is to collect process data on an as-needed basis. The reason JDI's ClassPrepareEvent is interesting is that, when you launch a debug target process, or even when you attach to an already-running process, it's likely that some of your desired breakpoints lie in classes which have not yet been loaded.
In my usual scenario, I call the com.sun.jdi.VirtualMachine's allClasses() method to get a list of all loaded ReferenceTypes. One way to think of a ReferenceType is as a chunk of a Java class definition. If your Java class has inner classes, then they will be broken out by JDI into separate ReferenceTypes. Each ReferenceType contains a collection of line locations; these correspond to lines of code on which breakpoints can be set and are identified by (among other things) the source-code line number. If a line of source code cannot be the target of a breakpoint, then there will not be a line location for it in the ReferenceType. In my debugger-based applications, I step through the line locations of all the ReferenceTypes, matching up line locations with breakpoint specifications, and then register my breakpoint requests.
As you can guess, I have a potential problem: what should I do if a class I need has not yet been loaded at the time I'm constructing my breakpoint requests? The answer is: JDI's ClassPrepareEvent. The entry point for using this part of the API is the EventRequestManager's createClassPrepareRequest() method. Having made our request, the same event-listener loop we use to wait for breakpoint events can also be used to wait for class prepare events (see the JVM specification for a definition of class preparation).
One thing I remember from my previous development on this API is that there is a timing risk here. You probably want to create the class prepare request before you iterate over the list of currently-loaded classes. The reason is that you don't want to fall into this trap:
- Iterate over a set of the currently-loaded classes, processing and making breakpoint requests.
- Suddenly, a class you need is loaded!
- You register your class-prepare event and start getting events as classes are loaded, but you miss the class that loaded in between step #1 and step #3.
- Register for class-prepare events so you don't get caught by the above issue.
- Iterate over the currently-loaded classes, requesting breakpoints as necessary.
- Process newly-loaded classes, requesting breakpoints as necessary.
Whether you launch your target application from your debugger or attach to it after the fact, you will have to deal with some variation of this issue. The way I deal with it is to add some state to the class I use to define each breakpoint specification. As each corresponding loaded class is found and the breakpoint request is made, I set a flag on the specification so that I know the request was registered. Further, I follow the second approach outlined above (better to have duplicates than to miss one). If I see a class-prepare event for a class I've already processed from the VM's ReferenceType list, then I simply skip over it. I do the same for the reverse situation, in which my list of ReferenceTypes contains ReferenceTypes which I have just processed in my ClassPrepareEvent listener.
Finally, one issue I have not looked at before (either for this development effort, or in my previous development in this area) -- what happens when a class is unloaded, especially a class on which you have registered breakpoint requests. For example, will a registered breakpoint request prevent a class from being unloaded? Do you care about a stranded breakpoint request if the class isn't even loaded? (Answer: yes, I suppose, if it gets reloaded and you no longer have a valid breakpoint request for it). JDI does have a ClassUnloadEvent, for which you can also register a listener. As I said, I have not dealt with this (possible) issue, having never seen a target class get unloaded before, but it's good to know "there's an API for that".