DTrace Topics Java

From Siwiki

Jump to: navigation, search

Contents

[edit] DTrace Topics: Java

This article is about DTracing Java, and is part of the DTrace Topics collection. A general understanding of DTrace is assumed knowledge, which can be studied from the DTrace Topics: Intro section.

DTrace is a dynamic troubleshooting and analysis tool first introduced in the Solaris 10 and OpenSolaris operating systems. Java is an object oriented programming language created by Sun. DTracing Java is about observing the operation of Java code using DTrace.

Completion Image:trafficlight_yellow02.png
Difficulty Image:coffeemug01.png Image:coffeemug01.png Image:coffeemug01.png
Audience Java developers, application support

[edit] Java and DTrace

In the first release of Solaris 10, DTrace provided a jstack() action to read Java stack traces.

For JDK 1.4.2 and 5.0, prototype DTrace Java providers were released as loadable VM agent libraries, first named “djvm” then “dvm”.

The hotspot DTrace Java provider was integrated in JDK 6.0, ready for immediate use.

[edit] jstack()

jstack() prints a stack trace with Java translations. Since jstack() is a DTrace action, one must pick a probe from a provider to begin using it. Appropriate providers include,

  • The profile provider, to sample frequent stack traces. This can identify Java code hot spots.
  • The pid provider, to show how Java interacts with user libraries.
  • The syscall provider, to determine what Java causes the system to do, such as I/O.

Long Java stack traces can exceed the default string buffers that DTrace has allocated. If you suspect this has happened (DTrace may warn you if it did), you can increase the global tunables jstackframes and jstackstrsize.

[edit] Example #1

The following DTrace one-liner matches all processes with the execname "java", when they call the read() syscall, and prints jstack(). The first line of the output shows that a syscall was entered, followed by user libraries (libc.so.1 through to libmawt.so.4), and then followed by Java classes and Java methods, then back into user libraries (libjvm.so).

# dtrace -n 'syscall::read:entry /execname == "java"/ { jstack(); }'
dtrace: description 'syscall::read:entry ' matched 1 probe
[...]
CPU     ID                    FUNCTION:NAME
  0  75943                       read:entry 
              libc.so.1`_read+0x7
              libX11.so.4`_X11TransSocketRead+0x25
              libX11.so.4`_X11TransRead+0x17
              libX11.so.4`_XRead+0x58
              libX11.so.4`_XEventsQueued+0x29e
              libX11.so.4`XEventsQueued+0x4f
              libmawt.so`Java_sun_awt_X11_XlibWrapper_XEventsQueued+0x1f
              sun/awt/X11/XlibWrapper.XEventsQueued(JI)I
              sun/awt/X11/XToolkit.run(Z)V
              sun/awt/X11/XToolkit.run()V
              java/lang/Thread.run()V
              StubRoutines (1)
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x1a3
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v2468_v_+0x27
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x2f
              libjvm.so`__1cJJavaCallsMcall_virtual6FpnJJavaValue_nLKlassHandle_nMsymbolHandle_4pnRJavaCallArguments_pnGThread__v_+0xc1
              libjvm.so`__1cJJavaCallsMcall_virtual6FpnJJavaValue_nGHandle_nLKlassHandle_nMsymbolHandle_5pnGThread__v_+0x7e
              libjvm.so`__1cMthread_entry6FpnKJavaThread_pnGThread__v_+0xd2
              libjvm.so`__1cKJavaThreadRthread_main_inner6M_v_+0x4c
              libjvm.so`__1cKJavaThreadDrun6M_v_+0x196
              libjvm.so`java_start+0xd3
              libc.so.1`_thr_setup+0x52
              libc.so.1`_lwp_start

This is awesome stuff - we can see the layers of the software stack, and how they are interacting. This is invaluable for troubleshooting - it can answer why Java is causing the system to do various tasks.

Note that the above output contained some encoded C++ names; this can be translated using the c++filt tool,

# dtrace -n 'syscall::read:entry /execname == "java"/ { jstack(); }' | c++filt
dtrace: description 'syscall::read:entry ' matched 1 probe
[...]
CPU     ID                    FUNCTION:NAME
  0  75943                       read:entry 
              libc.so.1`_read+0x7
              libX11.so.4`_X11TransSocketRead+0x25
              libX11.so.4`_X11TransRead+0x17
              libX11.so.4`_XRead+0x58
              libX11.so.4`_XEventsQueued+0x29e
              libX11.so.4`XEventsQueued+0x4f
              libmawt.so`Java_sun_awt_X11_XlibWrapper_XEventsQueued+0x1f
              sun/awt/X11/XlibWrapper.XEventsQueued(JI)I
              sun/awt/X11/XToolkit.run(Z)V
              sun/awt/X11/XToolkit.run()V
              java/lang/Thread.run()V
              StubRoutines (1)
              libjvm.so`void JavaCalls::call_helper(JavaValue*,methodHandle*,JavaCallArguments*,Thread*)+0x1a3
              libjvm.so`void os::os_exception_wrapper(void(*)(JavaValue*,methodHandle*,JavaCallArguments*,Thread*),JavaValue*,methodHandle*,JavaCallArguments*,Thread*)+0x27
              libjvm.so`void JavaCalls::call(JavaValue*,methodHandle,JavaCallArguments*,Thread*)+0x2f
              libjvm.so`void JavaCalls::call_virtual(JavaValue*,KlassHandle,symbolHandle,symbolHandle,JavaCallArguments*,Thread*)+0xc1
              libjvm.so`void JavaCalls::call_virtual(JavaValue*,Handle,KlassHandle,symbolHandle,symbolHandle,Thread*)+0x7e
              libjvm.so`void thread_entry(JavaThread*,Thread*)+0xd2
              libjvm.so`void JavaThread::thread_main_inner()+0x4c
              libjvm.so`void JavaThread::run()+0x196
              libjvm.so`java_start+0xd3
              libc.so.1`_thr_setup+0x52
              libc.so.1`_lwp_start

This is a little awkward to use due to pipe buffering between dtrace and c++filt; it may be better to write to a file and c++filt afterwards.

[edit] string table overflow

Translated Java stack traces can breach some string limits producing truncated output and warning messages, for example,

  1  79901                       read:entry
              libc.so.1`_read+0x7
              libzip.so`ZFILE_read+0x24
              libzip.so`readFully+0x57
              libzip.so`readFullyAt+0x44
              libzip.so`ZIP_GetEntryDataOffset+0x48
              libzip.so`ZIP_Read+0x92
              libzip.so`Java_java_util_zip_ZipFile_read+0x52
              java/util/zip/ZipFile.read(JJJ[BII)I
              java/util/zip/ZipFile.access$1200(JJJ[BII)I
              java/util/zip/ZipFile$ZipFileInputStream.read([BII)I
              java/util/zip/ZipFile$1.fill()V
              java/util/zip/InflaterInputStream.read([BII)I
              sun/misc/Resource.getBytes()[B
              java/net/URLClassLoader.defineClass(Ljava/lang/String;Lsun/misc/Resource;)Ljava/lang/Class;
              java/net/URLClassLoader.access$000(Ljava/net/URLClassLoader;Ljava/lang/String;Lsun/misc/Resource;)Ljava/lang/Class;
              java/net/URLClassLoader$1.run()Ljava/lang/Object;
              Stub^A
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x1a3
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v2468_v_+0x27
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x2f
              libjvm.so`JVM_DoPrivileged+0x35c
              libjava.so`Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2Ljava_security_AccessControlContext_2+0x2b
              0xfb20a578
              0xfb202d67
              0xfb202d67
              0xfb202d67
              0xfb202d67
              0xfb202d67
              0xfb200202
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x1a3
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v2468_v_+0x27
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x2f
              libjvm.so`__1cJJavaCallsMcall_special6FpnJJavaValue_nLKlassHandle_nMsymbolHandle_4pnRJavaCallArguments_pnGThread__v_+0x66
              libjvm.so`__1cJJavaCallsMcall_special6FpnJJavaValue_nGHandle_nLKlassHandle_nMsymbolHandle_53pnGThread__v_+0x8e
              libjvm.so`__1cQSystemDictionaryTload_instance_class6FnMsymbolHandle_nGHandle_pnGThread__nTinstanceKlassHandle__+0x165
              libjvm.so`__1cQSystemDictionarybEresolve_instance_class_or_null6FnMsymbolHandle_nGHandle_2pnGThread__pnMklassOopDesc__+0x514
              libjvm.so`__1cQSystemDictionaryPresolve_or_null6FnMsymbolHandle_nGHandle_2pnGThread__pnMklassOopDesc__+0x63
              libjvm.so`__1cQSystemDictionaryPresolve_or_fail6FnMsymbolHandle_nGHandle_2bpnGThread__pnMklassOopDesc__+0x2a
              libjvm.so`__1cTconstantPoolOopDescNklass_at_impl6FnSconstantPoolHandle_ipnGThread__pnMklassOopDesc__+0x1a1
              libjvm.so`__1cSInterpreterRuntimeE_new6FpnKJavaThread_pnTconstantPoolOopDesc_i_v_+0x68
              0xfb212ba4
              0xfb202ecd
              0xfb202ecd
              0xfb2033a9
              0xfb202ecd
              0xfb2033a9
              0xfb202ecd
              0xfb2033a9
              0xfb202ecd
              0xfb202ecd

dtrace: 156 jstack()/ustack() string table overflows

This problem can be reduced or eliminated by increasing jstacksize,

# dtrace -x jstackstrsize=2048 ...

[edit] djvm/dvm Provider

If possible, move the application to to JDK 6.0 so that the integrated hotspot provider can be used instead.

If you are stuck on JDK 1.4.2 or 5.0, you can try the djvm/dvm prototype provider. They require command line configuration and the application to be restarted.

They can be downloaded from Prototype Java Provider for JDK 1.4.2 or JDK 5.0

Some examples of its usage are here. Several more examples and all the Java related scripts from the DTraceToolkit back ported to the dvm provider can be found here.

[edit] hotspot Provider

The hotspot provider was integrated into JDK 6.0. You can check what java you are running by default using,

$ java -version
java version "1.6.0"
Java(TM) SE Runtime Environment (build 1.6.0-b105)
Java HotSpot(TM) Server VM (build 1.6.0-b105, mixed mode)

If you have downloaded and added a beta or "early access" version of JDK 6.0, it may appear in a different directory. Try the following command to see what else is available,

$ ls /usr/jdk/instances/*/bin/java
/usr/jdk/instances/jdk1.5.0/bin/java  /usr/jdk/instances/jdk1.6.0/bin/java

[edit] hotspot Probes

hotspot provides around 500 different probes, including:

Selected hotspot Probes
probe name description
class-loaded A class loaded
class-unloaded A class unloaded
method-entry A method begins
method-return A method completed
object-alloc An object was allocated
gc-begin System wide GC begins
gc-end System wide GC ended
thread-start A thread has started
thread-stop A thread completed

The method-* probes can degrade performance, and are only enabled with the VM flag ExtendedDTraceProbes.

The full reference for probes and their arguments is: hotspot Probes

[edit] hotspot Example #1

The hotspot provider will be demonstrated by tracing a simple Java program.

The following Greeting.java code may look familiar, many Java tutorials begin with something similar,

$ cat Greeting.java 
public class Greeting {
        public void greet() {
                System.out.println("Hello DTrace!");
        }
} 

Now the test harness,

$ cat TestGreeting.java 
public class TestGreeting {
        public static void main(String[] args) {
                Greeting hello = new Greeting();
                while (true) {
                        hello.greet();
                        try {
                                Thread.sleep(1000);
                        } catch (InterruptedException e) {
                        }
                }
        }
}

Compiling, Executing,

$ javac TestGreeting.java 
$ java TestGreeting 
Hello DTrace!
Hello DTrace!
Hello DTrace!
Hello DTrace!
^C

This simple program produces some known events every second, as it writes the "Hello DTrace!" message to the screen.

[edit] jstack()

We will start with jstack() (not hotspot!). We know that this program writes output, ie syscall::write, so we will use that as the probe for our jstack() action,

# dtrace -n 'syscall::write:entry /execname == "java"/ { jstack(); }'
dtrace: description 'syscall::write:entry ' matched 1 probe
[...]
  1  79903                      write:entry 
              libc.so.1`_write+0x7
              libjvm.so`__1cDhpiFwrite6FipkvI_I_+0xa0
              libjvm.so`JVM_Write+0x36
              libjava.so`writeBytes+0x154
              libjava.so`Java_java_io_FileOutputStream_writeBytes+0x3f
              java/io/FileOutputStream.writeBytes([BII)V
              java/io/FileOutputStream.write([BII)V
              java/io/BufferedOutputStream.flushBuffer()V
              java/io/BufferedOutputStream.flush()V
              java/io/PrintStream.write([BII)V
              sun/nio/cs/StreamEncoder.writeBytes()V
              sun/nio/cs/StreamEncoder.implFlushBuffer()V
              sun/nio/cs/StreamEncoder.flushBuffer()V
              java/io/OutputStreamWriter.flushBuffer()V
              java/io/PrintStream.newLine()V
              java/io/PrintStream.println(Ljava/lang/String;)V
              Greeting.greet()V
              TestGreeting.main([Ljava/lang/String;)V
              StubRout
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x1a3
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v2468_v_+0x27
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x2f
              libjvm.so`__1cRjni_invoke_static6FpnHJNIEnv__pnJJavaValue_pnI_jobject_nLJNICallType_pnK_jmethodID_pnSJNI_ArgumentPusher_pnGThread__v_+0x1df
              libjvm.so`jni_CallStaticVoidMethod+0x15d
              java`JavaMain+0xd30
              libc.so.1`_thr_setup+0x52
              libc.so.1`_lwp_start

The stack begins in the user level libraries libc, libjvm, libjava, then enters Java. Class and method names are visible, for example java/io/FileOutputStream.write.

Study the Java he stack trace carefully,

  • Can you find Greeting.greet?
  • Who calls Greeting.greet?
  • What is above Greeting.greet? How many of these classes do you recognize?

[edit] Listing Probes

The hotspot provider make many probes available to trace Java and JVM events. As root we can list all of the probes available, while the TestGreeting program is running (and making these probes available to see),

# dtrace -ln 'hotspot*:::'
   ID   PROVIDER            MODULE                          FUNCTION NAME
58974 hotspot_jni120546         libjvm.so      jni_CallNonvirtualIntMethodA CallNonvirtualIntMethodA-return
58975 hotspot_jni120546         libjvm.so      jni_CallNonvirtualIntMethodV CallNonvirtualIntMethodV-entry
58976 hotspot_jni120546         libjvm.so      jni_CallNonvirtualIntMethodV CallNonvirtualIntMethodV-return
58977 hotspot_jni120546         libjvm.so      jni_CallNonvirtualLongMethod CallNonvirtualLongMethod-entry
58978 hotspot_jni120546         libjvm.so      jni_CallNonvirtualLongMethod CallNonvirtualLongMethod-return
58979 hotspot_jni120546         libjvm.so     jni_CallNonvirtualLongMethodA CallNonvirtualLongMethodA-entry
58980 hotspot_jni120546         libjvm.so     jni_CallNonvirtualLongMethodA CallNonvirtualLongMethodA-return
58981 hotspot_jni120546         libjvm.so     jni_CallNonvirtualLongMethodV CallNonvirtualLongMethodV-entry
58982 hotspot_jni120546         libjvm.so     jni_CallNonvirtualLongMethodV CallNonvirtualLongMethodV-return
58983 hotspot_jni120546         libjvm.so    jni_CallNonvirtualObjectMethod CallNonvirtualObjectMethod-entry
58984 hotspot_jni120546         libjvm.so    jni_CallNonvirtualObjectMethod CallNonvirtualObjectMethod-return
58985 hotspot_jni120546         libjvm.so   jni_CallNonvirtualObjectMethodA CallNonvirtualObjectMethodA-entry
58986 hotspot_jni120546         libjvm.so   jni_CallNonvirtualObjectMethodA CallNonvirtualObjectMethodA-return
58987 hotspot_jni120546         libjvm.so   jni_CallNonvirtualObjectMethodV CallNonvirtualObjectMethodV-entry
58988 hotspot_jni120546         libjvm.so   jni_CallNonvirtualObjectMethodV CallNonvirtualObjectMethodV-return
58989 hotspot_jni120546         libjvm.so     jni_CallNonvirtualShortMethod CallNonvirtualShortMethod-entry
58990 hotspot_jni120546         libjvm.so     jni_CallNonvirtualShortMethod CallNonvirtualShortMethod-return
58991 hotspot_jni120546         libjvm.so    jni_CallNonvirtualShortMethodA CallNonvirtualShortMethodA-entry
58992 hotspot_jni120546         libjvm.so    jni_CallNonvirtualShortMethodA CallNonvirtualShortMethodA-return
58993 hotspot_jni120546         libjvm.so    jni_CallNonvirtualShortMethodV CallNonvirtualShortMethodV-entry
58994 hotspot_jni120546         libjvm.so    jni_CallNonvirtualShortMethodV CallNonvirtualShortMethodV-return
58995 hotspot_jni120546         libjvm.so      jni_CallNonvirtualVoidMethod CallNonvirtualVoidMethod-entry
58996 hotspot_jni120546         libjvm.so      jni_CallNonvirtualVoidMethod CallNonvirtualVoidMethod-return
58997 hotspot_jni120546         libjvm.so     jni_CallNonvirtualVoidMethodA CallNonvirtualVoidMethodA-entry
[...]

Lots of probes. How many probes?

# dtrace -ln 'hotspot*:::' | wc -l
    1015
# dtrace -ln 'hotspot*:::' | awk '{print $5}' | sort -u | wc -l
     499

Minus the header, that gives 1014 probes. Some of the probe names are listed more than once, as they have more than one entry point in the Java source. awk and sort -u listed unique lines, showing that there were 498 unique probe names.

Now for enabling the probes, this time without the -l for listing,

# dtrace -n 'hotspot*:::' 
dtrace: description 'hotspot*:::' matched 1014 probes
CPU     ID                    FUNCTION:NAME
  0  66465 jni_GetArrayLength:GetArrayLength-entry 
  0  66466 jni_GetArrayLength:GetArrayLength-return 
  0  66529 jni_GetObjectField:GetObjectField-entry 
  0  66530 jni_GetObjectField:GetObjectField-return 
  0  66529 jni_GetObjectField:GetObjectField-entry 
  0  66530 jni_GetObjectField:GetObjectField-return 
  0  66475 jni_GetByteArrayRegion:GetByteArrayRegion-entry 
  0  66476 jni_GetByteArrayRegion:GetByteArrayRegion-return 
  0  66456 jni_ExceptionOccurred:ExceptionOccurred-entry 
  0  66457 jni_ExceptionOccurred:ExceptionOccurred-return 
[...]

The output scrolls rather fast...

[edit] Aggregating Probes

Just aggregating probe names, a simple one-liner, can give a consise insight into Java behaviour. The TestGreeting program is still running, and the following was run for a few seconds,

# dtrace -n 'hotspot*::: { @[probename] = count(); }'
dtrace: description 'hotspot*::: ' matched 1014 probes
^C

  ExceptionOccurred-entry                                           8
  ExceptionOccurred-return                                          8
  GetArrayLength-entry                                              8
  GetArrayLength-return                                             8
  GetByteArrayRegion-entry                                          8
  GetByteArrayRegion-return                                         8
  GetObjectField-entry                                             16
  GetObjectField-return                                            16

Interesting, but some expected probes are missing (eg, method-entry). To see those we need to enable extended probes.

Extended probes are not activated by default as they may degrade performance on busy apps. They can be enabled using,

  • An option to java: -XX:+ExtendedDTraceProbes
  • An option to jinfo: -flag +ExtendedDTraceProbes
# java -XX:+ExtendedDTraceProbes TestGreeting
Hello DTrace!
Hello DTrace!
Hello DTrace!
[...]

and now aggregating,

# dtrace -n 'hotspot*::: { @[probename] = count(); }'
dtrace: description 'hotspot*::: ' matched 1014 probes
^C

  ExceptionOccurred-entry                                           8
  ExceptionOccurred-return                                          8
  GetArrayLength-entry                                              8
  GetArrayLength-return                                             8
  GetByteArrayRegion-entry                                          8
  GetByteArrayRegion-return                                         8
  object-alloc                                                      8
  GetObjectField-entry                                             16
  GetObjectField-return                                            16
  method-entry                                                    496
  method-return                                                   496

Now we see method-entry and method-return, which occurred 496 times while this was tracing (about 4 seconds).

[edit] Tracing Methods

The class and method names can be fetched from the probe arguments. The following (longer) one-liner snoops Java method execution, as the TestGreeting program runs,

# dtrace -qn 'hotspot*:::method-entry { printf("-> %4s.%s\n",
  stringof(copyin(arg1, arg2)), stringof(copyin(arg3, arg4))); }'
-> Greeting.greet
-> java/io/PrintStream.println
-> java/io/PrintStream.print
-> java/io/PrintStream.write
-> java/io/PrintStream.ensureOpen
-> java/io/Writer.write
-> java/io/BufferedWriter.write
-> java/io/BufferedWriter.ensureOpen
-> java/io/BufferedWriter.min
-> java/lang/String.getChars
-> java/lang/System.arraycopy
-> java/io/BufferedWriter.flushBuffer
-> java/io/BufferedWriter.ensureOpen
-> java/io/OutputStreamWriter.write
-> sun/nio/cs/StreamEncoder.write
-> sun/nio/cs/StreamEncoder.ensureOpen
-> sun/nio/cs/StreamEncoder.implWrite
-> java/nio/CharBuffer.wrap
-> java/nio/HeapCharBuffer.<init>
-> java/nio/CharBuffer.<init>
-> java/nio/Buffer.<init>
-> java/lang/Object.<init>
-> java/nio/Buffer.limit
-> java/nio/Buffer.position
-> java/nio/Buffer.hasRemaining
-> java/nio/charset/CharsetEncoder.encode
-> sun/nio/cs/US_ASCII$Encoder.encodeLoop
[...]

Awsome! We can see live class.method calls!

If you see no output, try enabling the ExtendedDTraceProbes option first. If you think you see shuffled output, you may be on a multi-CPU server which is dumping CPU buffers every second but not post sorting; you will need to print out the timestamp variable in the DTrace action and post sort (eg, /usr/bin/sort -n).

This output can scroll very fast for busy applications. It may be more appropriate to aggregate method calls.

[edit] Aggregating Methods

The following script, jagg.d, traces and summarizes Java method calls. It uses a DTrace aggregate to print a frequency count,

# ./jagg.d 
Tracing... Hit Ctrl-C to end.
^C
                                        Greeting.greet                       3
                          java/io/BufferedWriter.newLine                     3
                             java/io/PrintStream.newLine                     3
                             java/io/PrintStream.print                       3
                             java/io/PrintStream.println                     3
                                java/lang/Thread.sleep                       3
                    java/io/BufferedOutputStream.write                       6
                          java/io/BufferedWriter.flushBuffer                 6
                          java/io/BufferedWriter.min                         6
                          java/io/BufferedWriter.write                       6
                        java/io/FileOutputStream.write                       6
                        java/io/FileOutputStream.writeBytes                  6
                      java/io/OutputStreamWriter.flushBuffer                 6
                      java/io/OutputStreamWriter.write                       6
                                  java/io/Writer.write                       6
                                java/lang/Object.<init>                      6
                                java/lang/String.getChars                    6
                                java/lang/String.indexOf                     6
[...output truncated...]
                    java/io/BufferedOutputStream.flush                       9
                    java/io/BufferedOutputStream.flushBuffer                 9
                             java/io/PrintStream.write                       9
                          java/io/BufferedWriter.ensureOpen                 12
                             java/io/PrintStream.ensureOpen                 12
                                java/lang/System.arraycopy                  12
                             java/nio/ByteBuffer.array                      12
                    java/nio/charset/CoderResult.isUnderflow                12
                                 java/nio/Buffer.position                   18
                             java/nio/CharBuffer.arrayOffset                18
                             java/nio/ByteBuffer.arrayOffset                24

This has summarized the method calls from our TestGreeting program, which is still running. The most frequent methods were called 24 times during this sample. Our Greeting.greet method was called three times.

The source code to jagg.d is,

#!/usr/sbin/dtrace -qs

dtrace:::BEGIN
{
        printf("Tracing... Hit Ctrl-C to end.\n");
}

hotspot*:::method-entry
{
        this->class = (char *) copyin(arg1, arg2 + 1);
        this->class[arg2] = '\0';
        this->method = (char *) copyin(arg3, arg4 + 1);
        this->method[arg4] = '\0';
        @calls[stringof(this->class), stringof(this->method)] = count();
}

dtrace:::END
{
        printa("%48s.%-24s %@4d\n", @calls);
}

Note that strings are now manually terminated.

If you run a script which produces long class.method names such as "java/nio/ByteBuffer$EncoarrayOffsetodeArrayLoop.arrayOffsetodeArrayLoop", it is likely that you have string corruption and need to manually terminate the copyin class and methods names, as jagg.d does above.

[edit] Method Flow

With a little scripting, Java method flow tracing can be accomplished. The following simple script, jflow.d, prints an indented flow of class.method calls as they are entered and returned. We are still running our TestGreeting program, and the Greeting.greet class.method can be seen in the output,

# jflow.d 
 <- java/lang/Thread.sleep
 -> Greeting.greet
  -> java/io/PrintStream.println
   -> java/io/PrintStream.print
    -> java/io/PrintStream.write
     -> java/io/PrintStream.ensureOpen
     <- java/io/PrintStream.ensureOpen
     -> java/io/Writer.write
      -> java/io/BufferedWriter.write
       -> java/io/BufferedWriter.ensureOpen
       <- java/io/BufferedWriter.ensureOpen
       -> java/io/BufferedWriter.min
       <- java/io/BufferedWriter.min
       -> java/lang/String.getChars
        -> java/lang/System.arraycopy
        <- java/lang/System.arraycopy
       <- java/lang/String.getChars
      <- java/io/BufferedWriter.write
     <- java/io/Writer.write
     -> java/io/BufferedWriter.flushBuffer
      -> java/io/BufferedWriter.ensureOpen
      <- java/io/BufferedWriter.ensureOpen
      -> java/io/OutputStreamWriter.write
[...]

The jflow.d script was,

#!/usr/sbin/dtrace -s

#pragma D option quiet

hotspot*:::method-entry
{
        self->indent++;
        printf("%*s %s %s.%s\n", self->indent, "", "->",
            stringof(copyin(arg1, arg2)), stringof(copyin(arg3,arg4)));
}

hotspot*:::method-return
{
        printf("%*s %s %s.%s\n", self->indent, "", "<-",
            stringof(copyin(arg1, arg2)), stringof(copyin(arg3,arg4)));
        self->indent--;
}

jflow.d is neat as a demonstration of DTrace Java tracing, but it is too simple for much use in practice. Issues include,

  • The profile name "hotspot*" matches all instances of java. This should be "hotspot$target" to match individual java processes when the script is run with "-p PID" or "-c command".
  • As with other snooping tools that produce an event driven output, on a multi-CPU server the output may be shuffled.
  • The script doesn't account for multi-threaded Java programs. How should indentation be represented when we are switching between threads?
  • This will produce a lot of output on anything but trivial software. DTrace scripts that summarize rather than trace are likely to be more useful.
  • The script doesn't terminate strings (as with jagg.d).

The purpose of jflow.d is simply to show that we do have this level of observability.

[edit] Stack Flow

DTrace can observe all layers of the software stack, not just the Java method calls in the JVM. The following example demonstrates this capability, and was actually from the prototype provider. This was by Adam Leventhal,

   -> java/io/InputStreamReader:read
     -> sun/nio/cs/StreamDecoder:read
       -> sun/nio/cs/StreamDecoder:read0
         -> libc.so.1:malloc
           -> libc.so.1:_smalloc
           <- libc.so.1:_smalloc
         <- libc.so.1:malloc
         -> sun/nio/cs/StreamDecoder:read
           -> sun/nio/cs/StreamDecoder:ensureOpen
           <- sun/nio/cs/StreamDecoder:ensureOpen
           -> sun/nio/cs/StreamDecoder$CharsetSD:implRead
             -> java/nio/CharBuffer:wrap
               -> java/nio/HeapCharBuffer:
                 -> java/nio/CharBuffer:
                   -> java/nio/Buffer:
                     -> libc.so.1:malloc
                     <- libc.so.1:malloc
                     -> java/nio/Buffer:position
                     <- java/nio/Buffer:position
                   <- java/nio/Buffer:
                 <- java/nio/CharBuffer:
               <- java/nio/HeapCharBuffer:
             <- java/nio/CharBuffer:wrap
             -> java/nio/charset/CharsetDecoder:decode
               -> sun/nio/cs/US_ASCII$Decoder:decodeLoop
                 -> java/nio/ByteBuffer:hasArray
                 <- java/nio/ByteBuffer:hasArray
                 -> java/nio/CharBuffer:hasArray
                 <- java/nio/CharBuffer:hasArray
                 -> sun/nio/cs/US_ASCII$Decoder:decodeArrayLoop
[...]

While this extreme level of visibility is unlikely to be common usage, it is valuable that this is at all possible.

This really highlights why DTracing Java is different to other Java debugging and profiling software; DTrace can see all layers of the software stack. No matter where a bug or performance issue is hiding, DTrace has the capability to find it.

[edit] Object Allocation

The creation of a new object can be traced with object-alloc. Here we are back to tracing the TestGreeting program,

# dtrace -qn 'hotspot*:::object-alloc { printf("new %s\n", 
  stringof(copyin(arg1, arg2))); }'
new java/nio/HeapCharBuffer
new java/nio/HeapCharBuffer
new java/nio/HeapCharBuffer
new java/nio/HeapCharBuffer
[...]

[edit] Method Times

With method-entry and method-return probes, and DTrace's ability to measure nanosecond timestamps, the time to execute methods can be measured. This can help identify bottlenecks in Java code.

Things start to get a little harder. Be aware of,

  • overlapping methods
  • multiple Java threads executing concurrently
  • Java threads context switching off the CPUs
  • DTrace overheads at nanosecond resolutions
  • recursive methods?

It may be easier to use java -Xrunhprof profiling, which is built into the JVM and doesn't use DTrace. Here I use it on TestGreeting,

$ java -Xrunhprof:cpu=times,monitor=y,msa=y,depth=8,file=prof.txt TestGreeting
Hello DTrace!
Hello DTrace!
Hello DTrace!
Hello DTrace!
Hello DTrace!
Hello DTrace!
Hello DTrace!
^C
Dumping contended monitor usage ... CPU usage by timing methods ... done.
$
$ more prof.txt
JAVA PROFILE 1.0.1, created Mon Mar 19 20:41:08 2007

Header for -agentlib:hprof (or -Xrunhprof) ASCII Output (JDK 5.0 JVMTI based)

[...verbose output truncated...]

CPU TIME (ms) BEGIN (total = 84933) Mon Mar 19 20:41:15 2007
rank   self  accum   count trace method
   1  4.32%  4.32%       5 300309 sun.net.www.ParseUtil.decode
   2  3.20%  7.53%       1 301011 java.io.FilePermission$1.run
   3  2.59% 10.12%       1 300898 sun.net.www.protocol.file.Handler.createFileURLConnection
   4  2.41% 12.53%     276 300301 java.lang.StringBuilder.append
   5  2.31% 14.84%       1 300409 sun.misc.URLClassPath$JarLoader.getJarFile
   6  1.55% 16.39%       6 300428 sun.misc.URLClassPath$3.run
   7  1.46% 17.85%       5 300312 java.io.UnixFileSystem.normalize
   8  1.13% 18.98%     276 300300 java.lang.AbstractStringBuilder.append
   9  1.13% 20.11%     276 300299 java.lang.String.charAt
[...]
 122  0.15% 62.89%       7 301458 Greeting.greet
[...]

Profiling suggests that the Greeting.greet method consumes 0.15% CPU, with the highest being sun.net.www.ParseUtil.decode at 4.32% CPU.

[edit] More Analysis

So far we have seen a few ways to analyse the TestGreeting example program, and used a few different probes directly. There is a lot more which can be done with the hotspot provider, which provides around 500 different probes.

Further directions for analysis include,

  • Other hotspot probes
  • Other DTrace providers
  • Scripting to identify custom events of interest

The possibilities for analysis are near-endless. This is great if you are troubleshooting a known problem – as DTrace should have the power to find it.

[edit] hotspot Shipped Samples

In the /usr/jdk/instances/jdk1.6.0/sample/dtrace/hotspot and /usr/jdk/instances/jdk1.6.0/sample/dtrace/hotspot_jni directories are several shipped sample scripts of DTracing Java using the hotspot provider. These scripts are worth checking out, as their output is quite useful and they are shipped by default.

Since the shipped /usr/jdk/instances/jdk1.6.0/sample/dtrace/hotspot/README.txt file explains these scripts well, the contents of this README has been included as the three following sections,

[edit] README

From README.txt,

This directory contains D scripts which demonstrate usage of 'hotspot' provider probes.

The 'hotspot' provider makes available probes that can be used to track the lifespan of the VM, thread start and stop events, GC and memory pool statistics, method compilations, and monitor activity. With a startup flag, additional probes are enabled which can be used to monitor the running Java program, such as method enter and return probes, and object allocations. All of the hotspot probes originate in the VM library (libjvm.so), so they are also provided from programs which embed the VM.

Many of the probes in the provider have arguments that can be examined to provide further details on the state of the VM. Many of these probes' arguments are opaque IDs which can be used to link probe firings to each other, however strings and other data are also provided. When string values are provided, they are always present as a pair: a pointer to unterminated modified UTF-8 data (see JVM spec: 4.4.7) , and a length value which indicates the extent of that data. Because the string data (even when none of the characters are outside the ASCII range) is not guaranteed to be terminated by a NULL character, it is necessary to use the length-terminated copyinstr() intrinsic to read the string data from the process.

You can find more information about HotSpot probes here.

[edit] Scripts

From README.txt,

The following scripts/samples which demonstrate 'hotspot' probes usage are available:

  • class_loading_stat.d
    The script collects statistics about loaded and unloaded Java classes and
    dump current state to stdout every N seconds.
  • gc_time_stat.d
    The script measures the duration of a time spent in GC.
    The duration is measured for every memory pool every N seconds.
  • hotspot_calls_tree.d
    The script prints calls tree of fired 'hotspot' probes.
  • method_compile_stat.d
    The script prints statistics about N methods with largest/smallest
    compilation time every M seconds.
  • method_invocation_stat.d
    The script collects statistics about Java method invocations.
  • method_invocation_stat_filter.d
    The script collects statistics about Java method invocations.
    You can specify package, class or method name to trace.
  • method_invocation_tree.d
    The script prints tree of Java and JNI method invocations.
  • monitors.d
    The script traces monitor related probes.
  • object_allocation_stat.d
    The script collects statistics about N object allocations every M seconds.

[edit] Running the Scripts

From README.txt,

To run any D script from hotspot directory you can do either:

# dscript.d -c "java ..."

or if you don't have Solaris 10 patch which allows to specify probes that don't yet exist ( Hotspot DTrace probes are defined in libjvm.so and as result they could be not been yet loaded when you try to attach D script to the Java process) do:

# ../helpers/dtrace_helper.d -c "java ..." dscript.d

or if your application is already running you can just simply attach the D script like:

# dscript.d -p JAVA_PID

[edit] Quick Reference

The following is a quick reference for DTracing the Java programming language.

[edit] Providers

Provider Relavence
hotspot Tracing JVM events including Java method execution for JDK 1.6
dvm/djvm Tracing JVM events including Java method execution for JDK 1.4.2 and JDK 1.5
profile Sampling Java execution at fixed rates
syscall Tracing system calls that Java code caused
pid Tracing raw JVM operation

[edit] Probes

Probe Description
hotspot*:::method-entry A method begins (needs VM flag ExtendedDTraceProbes)
hotspot*:::method-returns A method completes (needs VM flag ExtendedDTraceProbes)
hotspot*:::object-alloc An object was created (needs VM flag ExtendedDTraceProbes)
hotspot*:::gc-begin System wide GC begins
hotspot*:::gc-ends System wide GC ended
hotspot*:::thread-start A thread begins execution
hotspot*:::thread-stop A thread completes

This is a small selection from about 500 probes available.

[edit] Capabilities

Capability Exists Format Example
Function entries yes Probe: hotspot:::method-entry # dtrace -qn 'hotspot*:::method-entry { printf("%s.%s\n",

stringof(copyin(arg1, arg2)), stringof(copyin(arg3, arg4))); }'

Function entry arguments no[1]
Function returns yes Probe: hotspot:::method-return # dtrace -qn 'hotspot*:::method-entry { printf("%s.%s\n",

stringof(copyin(arg1, arg2)), stringof(copyin(arg3, arg4))); }'

Function return value no[1]
Function instructions no[1]
Stack traces yes Action: jstack() dtrace -n 'syscall::read:entry /pid == $target/ { jstack(); }' -p PID

[1] this can probably be achieved with some effort using the pid provider to examine raw JVM execution.

[edit] Resources

Links for DTrace and Java,

  • hotspot Probes Guide
  • /usr/jdk/instances/jdk1.6.0/sample/dtrace/hotspot has some sample scripts
  • Search the Internet for “DTrace Java”, in particular are articles written by, Adam Leventhal, Jarod Jenson and Kelly O'Hair.

[edit] References

References used while creating this site,

Solaris Internals
Personal tools
The Books
The Ads