Your Java fell into my DTrace!

So I’ve been following with excitement the JVMPI and JVMTI agents prototyped by Jarod Jenson of Aeysis and developed by Kelly O’Hair of Sun (with some helpful guidance from Adam on Team DTrace). The agent exports DTrace probes that correspond to well-known Java phenomena (“method-entry”, “gc-start”, “vm-init”, etc), and while it isn’t a perfect solution from a DTrace perspective (the requirement that one restart the JVM means it’s still more appropriate for developers than for dynamic production use, for example), damn is it a big leap forward. For an example of its power, check out Adam’s blog entry following code flow from Java through native code and into the kernel (and back). Such a view of code flow is without precedent – it is awesome (in all senses of that word) to see the progression through the stack of software abstraction.

Wanting to get in on the fun, I’ve been playing around with it myself. To get a sample program, I downloaded Per Cerderberg’s Java implementation of Tetris. To get to run with the agent, I run it this way:

# export LD_LIBRARY_PATH=/path/to/djvm/build/i386/lib
# java -Xrundjvmti:all -cp $LD_LIBRARY_PATH -jar tetris-1.2-bin.jar

The “-Xrundjvmti” pulls in the JVMTI agent, and the “:all” following it denotes that I want to get DTrace probes for all possible events. (It defaults to less than all events to minimize probe effect.) Running with this agent creates a new provider in the JVM process called djvm. Listing its probes:

# dtrace -l -P 'djvm*'
ID   PROVIDER            MODULE                          FUNCTION NAME
4    djvm793      libdjvmti.so                  gc_finish_worker gc-stats
5    djvm793      libdjvmti.so         cbGarbageCollectionFinish gc-finish
6    djvm793      libdjvmti.so                     cbThreadStart thread-start
7    djvm793      libdjvmti.so                      _method_exit method-return
8    djvm793      libdjvmti.so                          cbVMInit vm-init
9    djvm793      libdjvmti.so                       cbThreadEnd thread-end
10    djvm793      libdjvmti.so           cbMonitorContendedEnter monitor-contended-enter
11    djvm793      libdjvmti.so                     cbMonitorWait monitor-wait
12    djvm793      libdjvmti.so                     _method_entry method-entry
13    djvm793      libdjvmti.so                  track_allocation object-alloc
14    djvm793      libdjvmti.so                   cbMonitorWaited monitor-waited
15    djvm793      libdjvmti.so         cbMonitorContendedEntered monitor-contended-entered
16    djvm793      libdjvmti.so                      cbObjectFree object-free
17    djvm793      libdjvmti.so          cbGarbageCollectionStart gc-start
18    djvm793      libdjvmti.so                      cbObjectFree class-unload
19    djvm793      libdjvmti.so                         cbVMDeath vm-death
20    djvm793      libdjvmti.so               cbClassFileLoadHook class-load
#

I can enable these just like any DTrace probe. For example, if I want to see output whenever a method is entered, I could enable the method-entry probe. This probe has two arguments: the first is a pointer to the class name, and the second is a pointer to the method name. These are both in user-level, so you need to use copyinstr() to get at them. So, to see which methods that Tetris is calling, it’s as simple as:

# dtrace -n djvm793:::method-entry'{printf("called %s.%s\n", copyinstr(arg0), co pyinstr(arg1))}' -q

Just dragging my mouse into the window containing Tetris generates a flurry of output:

called sun/awt/AWTAutoShutdown.notifyToolkitThreadBusy
called sun/awt/AWTAutoShutdown.getInstance
called sun/awt/AWTAutoShutdown.setToolkitBusy
called sun/awt/AWTAutoShutdown.isReadyToShutdown
called java/util/Hashtable.isEmpty
called java/awt/event/MouseEvent.<init>
called java/awt/event/InputEvent.<init>
called sun/reflect/DelegatingMethodAccessorImpl.invoke
called sun/reflect/GeneratedMethodAccessor1.invoke
called java/lang/Boolean.booleanValue
called java/awt/EventQueue.wakeup
called sun/awt/motif/MWindowPeer.handleWindowFocusIn
called java/awt/event/WindowEvent.<init>
called java/awt/event/WindowEvent.<init>
called java/awt/event/ComponentEvent.<init>
called java/awt/AWTEvent.<init>
called java/util/EventObject.<init>
called java/awt/SequencedEvent.<init>
called java/util/EventObject.getSource
called java/awt/AWTEvent.<init>
called java/util/EventObject.<init>
called java/util/LinkedList.add
called java/util/LinkedList.addBefore
called java/util/LinkedList$Entry.<init>
...

This is way too much output to sift through, so let’s instead aggregate on class and method name:

#pragma D option quiet
djvm$1:::method-entry
{
@[strjoin(strjoin(basename(copyinstr(arg0)), "."),
copyinstr(arg1))] = count();
}
END
{
printa("%-70s %8@d\n", @);
}

Running this (providing our pid – 793 – as the argument), fooling around with Tetris for a second or two, and then ^C‘ing gave me this output:

String.equals                                                                 1
PlatformFont.makeConvertedMultiFontString                                     1
String.toCharArray                                                            1
PlatformFont.makeConvertedMultiFontChars                                      1
CharToByteISO8859_1.getMaxBytesPerChar                                        1
CharToByteISO8859_1.reset                                                     1
CharToByteConverter.setSubstitutionMode                                       1
SquareBoard.getBoardHeight                                                    1
X11InputMethod.deactivate                                                     1
ExecutableInputMethodManager.setInputContext                                  1
SquareBoard$SquareBoardComponent.redrawAll                                    1
SquareBoard.clear                                                             1
Figure.getRotation                                                            1
InputMethodManager.getInstance                                                1
Figure.rotateRandom                                                           1
FontDescriptor.isExcluded                                                     1
CharToByteISO8859_1.canConvert                                                1
PlatformFont$PlatformFontCache.<init>                                         1
InputContext.deactivateInputMethod                                            1
...
SquareBoard.access$100                                                      807
AWTEvent.getID                                                              883
Figure.getRelativeY                                                         959
X11Renderer.drawLine                                                       1168
SunGraphics2D.drawLine                                                     1168
Rectangle.intersects                                                       1246
SquareBoard$SquareBoardComponent.paintSquare                               1246
SunGraphics2D.getClipBounds                                                1284
X11Renderer.validate                                                       1352
SurfaceData.getNativeOps                                                   1352
Rectangle.translate                                                        1361
Figure.getRelativeX                                                        1383
Rectangle2D.<init>                                                         1516
RectangularShape.<init>                                                    1516
SurfaceData.isValid                                                        1620
SunGraphics2D.getCompClip                                                  1620
Rectangle.<init>                                                           2839
SquareBoard.access$000                                                     8019
SquareBoard.access$200                                                     8496

That’s a lot of calls to the Rectangle constructor, so a natural question might be “where are calling this constructor?” For this, we can use the djvm provider along with the jstack() action:

djvm$1:::method-entry
/basename(copyinstr(arg0)) == "Rectangle" && copyinstr(arg1) == "<init>"/
{
@[jstack()] = count();
}

Running the above generated a bunch of output, with stack traces sorted in order of popularity; here was the most popular stacktrace:

libdjvmti.so`_method_entry+0x5c
com/sun/tools/dtrace/internal/Tracker._method_entry*
java/awt/Rectangle.<init>*
sun/java2d/SunGraphics2D.getClipBounds*
net/percederberg/tetris/SquareBoard$SquareBoardComponent.paintSquare*
net/percederberg/tetris/SquareBoard$SquareBoardComponent.paintComponent*
net/percederberg/tetris/SquareBoard$SquareBoardComponent.paint
net/percederberg/tetris/SquareBoard$SquareBoardComponent.redraw
net/percederberg/tetris/SquareBoard.update
net/percederberg/tetris/Figure.moveDown
0xf9002a7b
0xf9002a7b
0xf9002a7b
...

Note those Java frames: we can see exactly why we’re creating a new Rectangle. We might have all sorts of questions from the above output. The one that occurred to me was “just how much work do we do from that paint method?” Answering this question is a snap in DTrace using bread-and-butter like thread-locals and aggregations:

#pragma D option quiet
self int follow;
int ttl;
djvm$1:::method-entry
/self->follow/
{
@[basename(copyinstr(arg0)), copyinstr(arg1)] = count();
}
djvm$1:::method-entry,
djvm$1:::method-return
/basename(copyinstr(arg0)) == "SquareBoard$SquareBoardComponent" &&
copyinstr(arg1) == "paint"/
{
ttl += self->follow;
self->follow ^= 1;
}
END
{
normalize(@, ttl);
printa("%35s %35s %8@d\n", @);
}

Running the above for a bit and ^C‘ing generated ~200 lines of output; here are the last 30:

CompositeGlyphMapper                  getCachedGlyphCode        4
SquareBoard$SquareBoardComponent                      getDarkerColor       14
SquareBoard$SquareBoardComponent                     getLighterColor       14
X11Renderer                            fillRect       15
SunGraphics2D                            fillRect       15
SquareBoard                          access$100       19
Color                            hashCode       28
Color                              equals       28
Hashtable                                 get       29
Color                              getRGB       44
SunGraphics2D                            setColor       44
PixelConverter$Xrgb                          rgbToPixel       45
SurfaceType                            pixelFor       45
SurfaceData                            pixelFor       45
SquareBoard$SquareBoardComponent                         paintSquare       65
Rectangle                          intersects       65
SunGraphics2D                       getClipBounds       66
Rectangle                           translate       67
RectangularShape                              <init>       69
Rectangle2D                              <init>       69
X11Renderer                            drawLine      115
SunGraphics2D                            drawLine      115
SurfaceData                        getNativeOps      130
X11Renderer                            validate      130
SunGraphics2D                         getCompClip      134
SurfaceData                             isValid      134
Rectangle                              <init>      137
SquareBoard                          access$000      191
SquareBoard                          access$200      238

This means that (on average) each call to SquareBoard$SquareBoardComponent’s paint method induced 137 creations of Rectangle. Seeing this, I thought it would be cool to write a little “stat” utility that let me know how many of each kind of object was being constructed on a real-time, per-second basis. Here’s that script:

#pragma D option quiet
djvm$1:::method-entry
/copyinstr(arg1) == "<init>"/
{
@[basename(copyinstr(arg0))] = count();
}
profile:::tick-1sec
{
trunc(@, 20);
printf("%-70s %8s\n", "CLASS", "COUNT");
printa("%-70s %8@d\n", @);
printf("\n");
clear(@);
}

Running the above gives output once per-second. Initially, it didn’t provide any output – but as soon as I moused into the Tetris window, I saw a (small) explosion of mouse-related object construction:

CLASS                                                                     COUNT
Region                                                                        0
RectangularShape                                                              0
Rectangle2D                                                                   0
Rectangle                                                                     0
LinkedList$ListItr                                                            0
SentEvent                                                                     0
LinkedList$Entry                                                              0
WindowEvent                                                                   0
InvocationEvent                                                              15
HashMap$Entry                                                                69
ComponentEvent                                                               74
InputEvent                                                                   74
MouseEvent                                                                   74
WeakReference                                                                78
AWTEvent                                                                     79
EventObject                                                                  79
Point2D                                                                     128
Reference                                                                   156
EventQueueItem                                                              158
Point                                                                       192

As soon as I move the mouse out of the window, the rates dropped back to zero (as you would hope and expect from a well-behaved app). When I started actually playing Tetris, I saw a different kind of ouput:

CLASS                                                                     COUNT
Dimension                                                                     7
HashMap$Entry                                                                 8
EventObject                                                                   8
AWTEvent                                                                      8
ComponentEvent                                                                8
InputEvent                                                                    8
Finalizer                                                                    14
AffineTransform                                                              14
Graphics                                                                     14
SunGraphics2D                                                                14
Graphics2D                                                                   14
FinalReference                                                               14
EventQueueItem                                                               16
KeyEvent                                                                     16
WeakReference                                                                24
Region                                                                       42
Reference                                                                    62
Rectangle2D                                                                 105
RectangularShape                                                            105
Rectangle                                                                   175

This means that we’re seeing 175 Rectangle creations a second when I’m playing Tetris as fast as my BattleTris-hardened fingers can play. That doesn’t seem so bad, but you can easily see how useful this is going to be on those pathological Java apps!

Needless to say, the ability to peer into a Java app with DTrace is a very exciting development. To catch the fever, download the JVMTI and JVMPI agents, head to your nearest Solaris 10 machine (or download Solaris 10 if there isn’t one), and have at it!


Technorati tags: DTrace Java