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!