Currently, I am working on monitoring synchronization events in Java programs.
I have identified the following synchronization events:
- Thread.join (enter/leave)
- Thread.sleep (enter/leave)
- Thread.yield (enter/leave)
- Object.wait (enter/leave)
- synchronized blocks (try/enter/leave)
- synchronized (non-static) methods (try/enter/leave)
- synchronized static methods (try/enter/leave)
There are a few more events that I haven’t really considered yet, such as RMI events, volatile variables, and finalizers.
Some of the events above were marked “enter/leave” or “try/enter/leave”. That means that they actually correspond to several events: “enter/leave” generate events when the method is entered and left; “try/enter/leave” generate events when the program tries to enter the corresponding portion of code (“try”), when it has managed to enter it (“enter”), and when it has left it (“leave”).
An external monitor program (“monitor”) is attached to the program to be monitored (“slave”) using JPDA. This has the advantage that the slave runs virtually undisturbed. There are no additional threads, and if the slave crashes or hangs, the monitor still continues to run.
JPDA provides a great deal of access to the slave. Unfortunately, many of these accesses take a long time. I experimented with using JPDA’s monitoring of thread starts and deaths — after 30 minutes of loading DrJava and still not seeing the IDE, I gave up.
Our contingency plan right now is a mixed approach of using both JPDA and bytecode rewriting. The slave stores the events in a list on the slave side, and the monitor attaches to the program several times a second to read and clear it. This minimizes the number of accesses to the slave, but it also keeps the changes to the slave small.