Creating a Debugging and Profiling Agent with JVMTI

   
By C.K Prasad, Rajesh Ramchandani, Gopinath Rao, and Kim Levesque, June 24, 2004  

Articles Index

The Java Virtual Machine Tool Interface (JVMTI) provides a programming interface that allows you, the software developer, to create software agents that can monitor and control your Java programming language applications. JVMTI is new in the Java 2 Software Development Kit (SDK), Standard Edition, version 1.5.0. It replaces the Java Virtual Machine Profiling Interface (JVMPI), which had been included as an experimental feature of the Java 2 SDK since version 1.1. JVMTI is described in JSR-163.

This article illustrates how to use JVMTI to create a debugging and profiling tool for Java applications. Such a tool, also called an agent, uses the functionality exposed by the interface to register for notification of events as they occur in the application, and to query and control the application. JVMTI documentation is available here. A JVMTI agent can be useful for debugging and tuning an application. It can illustrate aspects of the application, such as memory allocation, CPU utilization, and lock contention.

 

Even though JVMPI is experimental, it is being used by many Java technology developers, and in several commercially-available Java application profilers. Please note that developers are strongly encouraged to use JVMTI instead of JVMPI. JVMPI will be discontinued in the very near future.


JVMTI improves upon the functionality and performance of JVMPI in many ways. For example:

  • JVMTI relies on a callback for each event. This is more efficient than the JVMPI design of using event structures, which needed to be marshalled and unmarshalled.
  • JVMTI contains four times as many functions as JVMPI (including many more functions to obtain information about variables, fields, methods, and classes). For a complete index of the JVMTI functions, see the Function Index page.
  • JVMTI provides notification for more types of events than does JVMPI, including exception events, field access and modification events, and breakpoint and single-step events.
  • Some of the JVMPI events that were never fully utilized, such as arena new and delete, or that can be better obtained through bytecode instrumentation, or the JVMTI functions themselves, (such as heap dump and object allocation) have been dropped. A description of the events is available at the Event Index page.
  • JVMTI is capability-based, whereas JVMPI was "all or nothing" with corresponding performance impact.
  • JVMPI heap functionality did not scale.
  • JVMPI had no error return information.
  • JVMPI was deeply invasive on VM implementations with resulting maintenance issues and performance impacts.
  • JVMPI is experimental and will be discontinued very soon.


In the remainder of this article, we present a simple agent that uses JVMTI functions to extract information from a Java application. The agent must be written in native code. The sample agent shown here is written in the C programming language. The following paragraphs describe how an agent is initialized, and how the agent uses JVMTI functions to extract information about a Java application, as well as how to compile and run the agent. The sample code and compilation steps are specific to UNIX environments, but can be modified for use with Windows. The agent described here can be used to analyze the threads and to determine JVM memory usage in any Java application.

A simple program written in the Java programming language, called SimpleThread.java, is included. We use ThreadSample.java to demonstrate the expected output from the agent.

The functionality of JVMTI is much more extensive than we can detail here, but the code in this article should provide a starting place for developing profiling tools to meet your own specific needs.

Agent Initialization

This section describes the code that is used to initialize the agent. To begin with, the agent must include the jvmti.h file with the statement: #include <jvmti.h>.

In addition, the agent must contain a function called Agent_OnLoad, which is invoked when the library is loaded. The Agent_OnLoad function is used to set up functionality that is required prior to initializing the Java virtual machine 1 (JVM). The Agent_OnLoad signature looks like this:

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
                  

 ...
                  

                   

 /* We return JNI_OK to signify success */
                  

 return JNI_OK;
                  

 }
                


In our sample code, we must enable several capabilities for the JVMTI functions and events that we will be using. It is generally desired, and in some cases required, to add these capabilities in the Agent_OnLoad function. The capabilities necessary for each function or event are described in the Java Virtual Machine Tool Interface pages. For example, to use the InterruptThread function, the can_signal_thread capability must be true. We set all of the capabilities needed for our sample code to true, and then add them to the JVMTI environment using the AddCapabilities function:

 static jvmtiEnv *jvmti = NULL;
                  

 static jvmtiCapabilities capa;
                  

 jvmtiError error;
                  

                   

 ...
                  

                   

 (void)memset(&capa, 0, sizeof(jvmtiCapabilities));
                  

 capa.can_signal_thread = 1;
                  

 capa.can_get_owned_monitor_info = 1;
                  

 capa.can_generate_method_entry_events = 1;
                  

 capa.can_generate_exception_events = 1;
                  

 capa.can_generate_vm_object_alloc_events = 1;
                  

 capa.can_tag_objects = 1;
                  

                   

 error = (*jvmti)->AddCapabilities(jvmti, &capa);
                  

 check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities.");
                  

                   

 ...
                


In addition, the Agent_OnLoad function is often used to register for notification of events. In our sample code, we enable several events, such as VM Initialization Event, VM Death Event, and VM Object Allocation, in Agent_OnLoad with the SetEventNotificationMode function as follows:

 error = (*jvmti)->SetEventNotificationMode
      (jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread)NULL);
                  

 error = (*jvmti)->SetEventNotificationMode
      (jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, (jthread)NULL);
                  

 error = (*jvmti)->SetEventNotificationMode
      (jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, (jthread)NULL);
                  

 check_jvmti_error(jvmti, error, "Cannot set event notification");
                  

                   

 ...
                


Note that in our example, NULL is passed as the third parameter, which enables the event notification globally. If desired, some events can be enabled or disabled for a particular thread.

Each event for which we register must also have a designated callback function, which will be called when the event occurs. For example, if a JVMTI Event of type Exception occurs, our example agent sends it to the callback method, callbackException().

This is done using the jvmtiEventCallbacks structure and SetEventCallbacks function:

jvmtiEventCallbacks callbacks;
                  

 ...
                  

                   

 (void)memset(&callbacks, 0, sizeof(callbacks));
                  

 callbacks.VMInit = &callbackVMInit; /* JVMTI_EVENT_VM_INIT */
                  

 callbacks.VMDeath = &callbackVMDeath; /* JVMTI_EVENT_VM_DEATH */
                  

 callbacks.Exception = &callbackException;/* JVMTI_EVENT_EXCEPTION */
                  

 callbacks.VMObjectAlloc = &callbackVMObjectAlloc;/* JVMTI_EVENT_VM_OBJECT_ALLOC */
                  

                   

 error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks,(jint)sizeof(callbacks));
                  

 check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks");
                  

                   

 ...
                


We also set up a global agent data area for use throughout our code.

/* Global agent data structure */
                  

 typedef struct {
                  

                   

 /* JVMTI Environment */
                  

 jvmtiEnv *jvmti;
                  

 jboolean vm_is_started;
                  

                   

 /* Data access Lock */
                  

 jrawMonitorID lock;
                  

 } GlobalAgentData;
                  

                   

 static GlobalAgentData *gdata;
                


In the Agent_OnLoad function, we perform the following setup:

/* Setup initial global agent data area
                  

* Use of static/extern data should be handled carefully here.
                  

* We need to make sure that we are able to cleanup after
                  

* ourselves so anything allocated in this library needs to be
                  

* freed in the Agent_OnUnload() function.
                  

*/
                  

                   

static GlobalAgentData data;
                  

(void)memset((void*)&data, 0, sizeof(data));
                  

gdata = &data;
                  

                   

...
                  

                   

/* Here we save the jvmtiEnv* for Agent_OnUnload(). */
                  

gdata->jvmti = jvmti;
                  

                   

...
                


We create a raw monitor in Agent_OnLoad(), then wrap the code of VM_INIT, VM_DEATH and EXCEPTION with JVMTI RawMonitorEnter() and RawMonitorExit() interfaces.

/* Here we create a raw monitor for our use in this agent to
                  

 * protect critical sections of code.
                  

 */
                  

                   

 error = (*jvmti)->CreateRawMonitor(jvmti, "agent data", &(gdata->lock));
                  

                   

 /* Enter a critical section by doing a JVMTI Raw Monitor Enter */
                  

                   

 static void
                  

 enter_critical_section(jvmtiEnv *jvmti)
                  

 {
                  

      jvmtiError error;
                  

                   

      error = (*jvmti)->RawMonitorEnter(jvmti, gdata->lock);
                  

      check_jvmti_error(jvmti, error, "Cannot enter with raw monitor");
                  

 }
                  

                   

 /* Exit a critical section by doing a JVMTI Raw Monitor Exit */
                  

                   

 static void
                  

 exit_critical_section(jvmtiEnv *jvmti)
                  

 {
                  

     jvmtiError error;
                  

     error = (*jvmti)->RawMonitorExit(jvmti, gdata->lock);
                  

     check_jvmti_error(jvmti, error, "Cannot exit with raw monitor");
                  

 }
                


Agent_OnUnload will be called by the VM when the agent is about to be unloaded. This function is used to clean-up resources allocated during Agent_OnLoad.

/* Agent_OnUnload: This is called immediately before the shared library
                  

 * is unloaded. This is the last code executed.
                  

 */
                  

                   

 JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
                  

 {
                  

 /* Make sure all malloc/calloc/strdup space is freed */
                  

 }
                


Analyzing Threads Using JVMTI

This section describes how to obtain information about user threads running in the JVM. As we have discussed, when the JVM is started, the startup function Agent_OnLoad in the JVMTI agent library is invoked. During VM initialization, a JVMTI Event of type JVMTI_EVENT_VM_INIT is generated and sent to the callbackVMInit routine in our agent code. Once the VM initialization event is received (that is, the VMInit callback is invoked), the agent can complete its initialization. Now, the agent is free to call any Java Native Interface (JNI) or JVMTI function. At this time, we are in the live phase and we will enable the Exception events ( JVMTI_EVENT_EXCEPTION) in this VMInit callback routine.

error = (*jvmti)->SetEventNotificationMode
                  

     (jvmti, JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL);
                


Exception events are generated whenever an exception is first detected in a Java programming language method. The exception may have beenthrown by a Java programming language or native method, but in the case of native methods, the event is not generated until the exception is first seen by a Java programming language method. If an exception is set and cleared in a native method, no exception event is generated.

For the purpose of demonstration, the sample Java application used is shown below. The main thread creates five threads, each of which throws an exception before exiting. Once the JVM is started, a JVMTI_EVENT_VM_INIT is generated and sent to the agent code for processing, as we have enabled VMInit and Exception events in our agent code. Later, when our Java thread throws an exception, a JVMTI_EVENT_EXCEPTION is sent to the agent code. The agent code then analyzes the thread information, and displays the current thread name, the thread group it belongs to, monitors owned by this thread, thread state, thread stack trace, and all the user threads in the JVM.

 public class SimpleThread {
                  

     static MyThread t;
                  

                   

     public static void main(String args[]) throws Throwable{
                  

                   

       t = new MyThread();
                  

       System.out.println("Creating and running 10 threads...");
                  

                   

       for(int i = 0; i < 5; i++) {
                  

          Thread thr = new Thread(t,"MyThread"+i);
                  

          thr.start();
                  

                   

       try {
                  

            thr.join();
                  

        } catch (Throwable t) {
                  

       }
                  

     }
                  

   }
                  

  }
                  

                   

 class MyThread implements Runnable {
                  

                   

    Thread t;
                  

                   

    public MyThread() {
                  

    }
                  

                   

    public void run() {
                  

     /* NO-OP */
                  

    try {
                  

                   

        "a".getBytes("ASCII");
                  

        throwException();
                  

        Thread.sleep(1000);
                  

    } catch (java.lang.InterruptedException e){
                  

        e.printStackTrace();
                  

    } catch (Throwable t) {
                  

   }
                  

 }
                  

                   

 public void throwException() throws Throwable{
                  

                   

   throw new Exception("Thread Exception from MyThread");
                  

   }
                  

 }
                


Let us take a look at the JVMTI agent code that is executed when an exception is thrown inside a Java application.

 throw new Exception("Thread Exception from MyThread");


A JVMTI exception event is generated and sent to the Exception callback routine in our agent code. The agent must add the capability can_generate_exception_events to enable the exception event. We use the JVMTI GetMethodName interface to display the method name and signature of the routine from which the exception was generated.

err3 = (*jvmti)->GetMethodName(jvmti, method, &name, &sig, &gsig);
                  

 printf("Exception in Method:%s%s\n", name, sig);
                


We use the JVMTI GetThreadInfo and GetThreadGroupInfo interfaces to display the current thread and group details.

err = (*jvmti)->GetThreadInfo(jvmti, thr, &info);
                  

                   

 if (err == JVMTI_ERROR_NONE) {
                  

 err1 = (*jvmti)->GetThreadGroupInfo(jvmti,info.thread_group, &groupInfo);
                  

                   

 ...
                  

 if ((err == JVMTI_ERROR_NONE) && (err1 == JVMTI_ERROR_NONE ))
                  

 {
                  

   printf("Got Exception event, Current Thread is : %s and Thread Group is: %s\n",
                  

 
        ((info.name==NULL) ? ""
                  

   : info.name), groupInfo.name);
                  

   }
                  

 }
                


This causes the following to be output on your terminal:

Got Exception event, Current Thread is : MyThread0 and Thread Group is: main

We can get information about the monitors owned by the specified thread by using the JVMTI GetOwnedMonitorInfo interface. This function does not require the thread to be suspended.

err = (*jvmti)->GetOwnedMonitorInfo(jvmti, thr, νm_monitors, &arr_monitors);
                  

 printf("Number of Monitors returned : %d\n", num_monitors);
                  

                


We can get state information for a thread using the JVMTI GetThreadState interface.

The thread state can be one of the following values:

  • Thread has been Terminated
  • Thread is Alive
  • Thread is runnable
  • Thread sleeping
  • Thread is waiting for Notification
  • Thread is in Object Wait
  • Thread is in Native
  • Thread is Suspended
  • Thread is Interrupted
err = (*jvmti)->GetThreadState(jvmti, thr, &thr_st_ptr);
                  

                   

 if ( thr_st_ptr & JVMTI_THREAD_STATE_RUNNABLE ) {
                  

                   

    printf("Thread: %s is Runnable\n", ((info.name==NULL) ? "" : info.name));
                  

    flag = 1;
                  

 }
                


Displaying All User Threads in the JVM Using JVMTI

The JVMTI function GetAllThreads is used to display all live threads known to the JVM. The threads are Java programming language threads attached to the VM.

The following code illustrates this:

 /* Get All Threads */
                  

 err = (*jvmti)->GetAllThreads(jvmti, &thr_count, &thr_ptr);
                  

 if (err != JVMTI_ERROR_NONE) {
                  

    printf("(GetAllThreads) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE,  err);
                  

    describe(err);
                  

    printf("\n");
                  

 }
                  

 if (err == JVMTI_ERROR_NONE && thr_count >= 1) {
                  

    int i = 0;
                  

    printf("Thread Count: %d\n", thr_count);
                  

                   

 for ( i=0; i < thr_count; i++) {
                  

                   

    /* Make sure the stack variables are garbage free */
                  

    (void)memset(&info1,0, sizeof(info1));
                  

                   

    err1 = (*jvmti)->GetThreadInfo(jvmti, thr_ptr[i], &info1);
                  

    if (err1 != JVMTI_ERROR_NONE) {
                  

       printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err1);
                  

       describe(err1);
                  

       printf("\n");
                  

    }
                  

                   

 printf("Running Thread#%d: %s, Priority: %d, context class loader:%s\n", i+1,info1.name,
                  

       info1.priority,(info1.context_class_loader == NULL ? ": NULL" : "Not Null"));
                  

                   

 /* Every string allocated by JVMTI needs to be freed */
                  

                   

 err2 = (*jvmti)->Deallocate(jvmti, (void*)info1.name);
                  

 if (err2 != JVMTI_ERROR_NONE) {
                  

     printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err2);
                  

     describe(err2);
                  

     printf("\n");
                  

    }
                  

   }
                  

 }
                


This causes the following to be output on your terminal:

                    Thread Count: 5
                  

                    Running Thread#1: MyThread4, Priority: 5, context class loader:Not Null
                  

                    Running Thread#2: Signal Dispatcher, Priority: 10, context class loader:Not Null
                  

                    Running Thread#3: Finalizer, Priority: 8, context class loader:: NULL
                  

                    Running Thread#4: Reference Handler, Priority: 10, context class loader:: NULL
                  

                    Running Thread#5: main, Priority: 5, context class loader:Not Null
                


Obtaining a JVM Thread Stacktrace

The JVMTI interface GetStackTrace can be used to get information about the stack of a thread. If max_count is less than the depth of the stack, the max_count number of deepest frames are returned, otherwise the entire stack is returned. The thread need not be suspended to call this function.

The following example causes up to five of the deepest frames to be returned. If there are any frames, the currently executing method name is also printed.

  /* Get Stack Trace */
                  

  err = (*jvmti)->GetStackTrace(jvmti, thr, 0, 5, &frames, &count);
                  

  if (err != JVMTI_ERROR_NONE) {
                  

     printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
                  

     describe(err);
                  

     printf("\n");
                  

 
    }
                  

  printf("Number of records filled: %d\n", count);
                  

  if (err == JVMTI_ERROR_NONE && count >=1) {
                  

                   

     char *methodName;
                  

     methodName = "yet_to_call()";
                  

     char *declaringClassName;
                  

     jclass declaring_class;
                  

                   

     int i=0;
                  

                   

 
     printf("Exception Stack Trace\n");
                  

     printf("=====================\n");
                  

     printf("Stack Trace Depth: %d\n", count);
                  

                   

     for ( i=0; i < count; i++) {
                  

             err = (*jvmti)->GetMethodName
                  

                 (jvmti, frames[i].method, &methodName, NULL, NULL);
                  

                  if (err == JVMTI_ERROR_NONE) {
                  

 
                err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class);
                  

                err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL);
                  

                if (err == JVMTI_ERROR_NONE) {
                  

                        printf("at method %s() in class %s\n", methodName, declaringClassName);
                  

                      }
                  

             }
                  

                   

     }
                  

                


This causes the following to be output on your terminal:

                    Number of records filled: 3
                  

                    Thread Stack Trace
                  

                    =====================
                  

                    Stack Trace Depth: 3
                  

                    at method throwException() in class LmyThread;
                  

                    at method run() in class LMyThread;
                  

                    at method run() in class Ljava/lang/Thread;
                  

                   

                


Analyzing the Heap Using JVMTI

This section describes the portion of the sample code that illustrates how to obtain information about heap usage. For example, we have registered for VM Object Allocation events as described in the section titled "Agent Initialization". This will notify us when the JVM has allocated an object that is visible to the Java programming language, and which is not detectable by other instrumentation mechanisms. This is an important difference from JVMPI, which sent an event when any object was allocated. In JVMTI, no event is sent for user-allocated objects, since it is expected that bytecode instrumentation can be used instead. For example, in the SimpleThread.java program, we will not be notified of the allocation of MyThread or Thread objects. An article demonstrating the use of bytecode instrumentation to obtain this information will be published separately.

The VM Object Allocation event is useful for determining information about objects allocated by the JVM. In the Agent_OnLoad method, we registered callbackVMObjectAlloc as the function to be called when the VM Object Allocation event was sent. The callback function parameters contain information about the object that has been allocated, such as the JNI local reference to the class of the object and the object size. With the jclass parameter, object_klass, we can use the GetClassSignature function to obtain information about the name of the class. We can print the object class and its size as shown below. Note that to avoid excessive output, we only print information about objects that are greater than 50 bytes.

/* Callback function for VM Object Allocation events */
                  

 static void JNICALL callbackVMObjectAlloc
    (jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, 
             jobject object, jclass object_klass, jlong size) {
                  

                   

 ...
                  

                   

 char *className;
                  

                   

 ...
                  

                   

 if (size > 50) {
                  

    err = (*jvmti)->GetClassSignature(jvmti, object_klass, &className, NULL);
                  

     if (className != NULL) {
                  

         printf("\ntype %s object allocated with size %d\n", className, (jint)size);
                  

 }
                  

 ...
                


We use the GetStackTrace method as described above to print the stack trace of the thread that is allocating the object. As that section describes, we obtain frames to a specified depth. The frames are returned as jvmtiFrameInfo structures, which contain each frame's jmethodID (that is, frames[x].method). The GetMethodName function can map the jmethodID to that particular method's name. Finally, in this example, we also use the GetMethodDeclaringClass and GetClassSignature functions to obtain the name of the class from which the method was called.

char *methodName;
                  

 char *declaringClassName;
                  

 jclass declaring_class;
                  

 jvmtiError err;
                  

                   

 //print stack trace
                  

 jvmtiFrameInfo frames[5];
                  

 jint count;
                  

 int i;
                  

                   

 err = (*jvmti)->GetStackTrace(jvmti, NULL, 0, 5, &frames, &count);
                  

 if (err == JVMTI_ERROR_NONE && count >= 1) {
                  

                   

     for (i = 0; i < count; i++) {
                  

          err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL);
                  

          if (err == JVMTI_ERROR_NONE) {
                  

                   

              err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class);
                  

              err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL);
                  

                   

     if (err == JVMTI_ERROR_NONE) {
                  

         printf("at method %s in class %s\n", methodName, declaringClassName);
                  

     }
                  

    }
                  

   }
                  

 }
                  

                   

 ...
                


Note that memory allocated to the char arrays by these functions should be freed when we are finished with them:

err = (*jvmti)->Deallocate(jvmti, (void*)className);
                  

 err = (*jvmti)->Deallocate(jvmti, (void*)methodName);
                  

 err = (*jvmti)->Deallocate(jvmti, (void*)declaringClassName);
                  

                   

 ...
                


The output from this code will look like this:

                    type Ljava/lang/reflect/Constructor; object allocated with size 64
                  

                    at method getDeclaredConstructors0 in class Ljava/lang/Class;
                  

                    at method privateGetDeclaredConstructors in class Ljava/lang/Class;
                  

                    at method getConstructor0 in class Ljava/lang/Class;
                  

                    at method getDeclaredConstructor in class Ljava/lang/Class;
                  

                    at method run in class Ljava/util/zip/ZipFile$1;
                


The returned name for primitive classes is the type signature character of the corresponding primitive type. For example, java.lang.Integer.TYPE is "I".

In our callback method for VM Object Allocation, we also use the IterateOverObjectsReachableFromObject function to demonstrate how we can obtain additional information about the heap. In our example, we pass as a parameter the JNI reference to the object that was just allocated, and the function will iterate over all objects that are directly and indirectly reachable from this newly allocated object. For each object that is reachable, another callback function is defined which can describe that reachable object. In our example, the callback function passed to the IterateOverObjectsReachableFromObject function is called reference_object:

err = (*jvmti)->IterateOverObjectsReachableFromObject
                  

      (jvmti, object, &reference_object, NULL);
                  

 if ( err != JVMTI_ERROR_NONE ) {
                  

    printf("Cannot iterate over reachable objects\n");
                  

 }
                  

                   

 ...
                


The reference_object function is defined as follows:

/* JVMTI callback function. */
                  

 static jvmtiIterationControl JNICALL
                  

 reference_object(jvmtiObjectReferenceKind reference_kind,
                  

 jlong class_tag, jlong size, jlong* tag_ptr,
                  

 jlong referrer_tag, jint referrer_index, void *user_data)
                  

                   

 {
                  

                   

 ...
                  

                   

 return JVMTI_ITERATION_CONTINUE;
                  

 }
                  

                   

 ...
                


In our example, we use the IterateOverObjectsReachableFromObject function to calculate both the combined size of all objects reachable from the newly allocated objects, as well as what types of objects they are. The object type is determined from the reference_kind parameter. We then print this information to receive output similar to the following:

                    This object has references to objects of combined size 21232
                  

                    This includes 45 classes, 9 fields, 1 arrays, 0 classloaders, 0 signers arrays,
                  

                    0 protection domains, 19 interfaces, 13 static fields, and 2 constant pools.
                  

                


Note that similar iteration functions available in JVMTI allow you to iterate over the entire heap (both reachable and unreachable objects), over the root objects and all objects that are directly and indirectly reachable from the root objects, or over all objects in the heap that are instances of a specified class. The technique for these functions is similar to that described previously. During the execution of these functions, the state of the heap does not change: no objects are allocated, no objects are garbage collected, and the state of objects (including held values) does not change. As a result, threads executing Java programming language code, threads attempting to resume the execution of Java programming language code, and threads attempting to execute JNI functions, are typically stalled. In the object reference callback functions, no JNI functions can be used, and no JVMTI functions can be used except those which are specifically allowed.

Compiling and Executing the Sample Code

To compile and run the code for the sample application described here, do the following:

  1. Set JDK_PATH to point to the J2SE 1.5 distribution.
    JDK_PATH="/home/xyz/j2sdk1.5.0/bin"
    


  2. Build the shared library using the C compiler. We used Sun Studio 8 C compiler.
    CC="/net/compilers/S1Studio_8.0/SUNWspro/bin/cc"
                          
    
     echo "...creating liba.so"
                          
    
     ${CC} -G -KPIC -o liba.so
                          
    
    -I${JDK_PATH}/include -I${JDK_PATH}/include/solaris a.c
                          
    
                        


  3. To load and run the agent library, you can use one of the following command-line arguments during VM startup.
    -agentlib:<jvmti-agent-library-name>
                          
    
    -agentpath:/home/foo/jvmti/<jvmti-agent-library-name>
                        


    and then you can run the sample Java application as follows:
    echo "...creating SimpleThread.class"
                          
    
    ${JDK_PATH}/bin/javac -g -d . SimpleThread.java
                          
    
    echo "...running SimpleThread.class"
                          
    
    LD_LIBRARY_PATH=. CLASSPATH=. ${JDK_PATH}/bin/java -showversion -agentlib:a SimpleThread
                        


Note: The sample agent code was built and tested on Solaris 9 Operating System.

Conclusion

In this article we demonstrated some of the interfaces that JVMTI provides for monitoring and management of the JVM. The JVMTI specification (JSR-163) is intended to provide a VM interface for the full breadth of tools that need access to VM state, including but not limited to: profiling, debugging, monitoring, thread analysis, and coverage analysis tools.

Developers are advised not to use JVMPI interfaces to develop tools or debugging utilities, as JVMPI is unsupported and experimental technology. JVMTI should be considered for writing any profiling and managing tools for Java virtual machines.

See Also
Authors

CK, Kim, Gopinath and Rajesh are part of Sun's Market Development Engineering group, working with Independent Software Vendors (ISV's) in their adoption efforts for various Sun technologies. They have written several publications in the areas of Solaris, Java and web services to demonstrate use of Sun technologies. You can reach this team at mde-jvmti@sun.com.



1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.