The JVMPI Transition to JVMTI

   
By Kelly O'Hair, July 1, 2004  

Articles Index

The Java Virtual Machine Tool Interface ( JVMTI) is a new native interface available in the Java 2 Software Development Kit (SDK), version 1.5.0. The Java Virtual Machine Profiling Interface ( JVMPI) is also available in the Java 2 Platform, Standard Edition (J2SE) SDK version 1.5.0, but has been deprecated in version 1.5.0. Transitioning from JVMPI to JVMTI may or may not be an easy task, depending on how JVMPI was used in the agent library. The intent of this article is to provide help in this transition and perhaps provide some general guidance in writing JVMTI agents. Make sure you have an installation of Java 2 SDK version 1.5.0 available to browse the JVMTI demos as you read this document, downloads are available on the J2SE 1.5.0 Beta 2 page.
Contents
 
Some History and Background
Transition Issues for JVMPI Agents
Some Basics on JVMTI Usage
Object Tagging and Heap Iteration
Summary
About the Author
 
Some History and Background
JVMTI provides for all the functional capabilities of the previous native interfaces JVMDI and JVMPI. However, it does not have many of the limitations of those older interfaces. Some of the JVMPI capabilities require the use of JVMTI and the technique called Byte Code Insertion ( BCI), sometimes referred to as Byte Code Injection or Byte Code Instrumentation. JVMTI allows for all JVM functionality to continue to operate, like JIT or JVM compilation and different GC implementations. Certain JVMTI features are controlled by asking for JVMTI Capabilities, and some of these can cause changes in JVM performance, but most features are available while the JVM is running "full speed". All JVMTI object handles are JNI handles, and JVMTI Event callbacks always include a JNIEnv* argument to facilitate JNI usage. Multiple JVMTI agents can operate in a single JVM and all interfaces return an error code to determine success or failure of the request. It is the intention of JVMTI to displace both JVMPI and JVMDI, and to ultimately be the single native tool interface into the JVM.
 
JVMPI was introduced in Java 2 SDK version 1.1, but was always labeled as a native "experimental" profiling interface. It was ported to the HotSpot Virtual Machine in Java 2 SDK version 1.3.0 but was never as stable as in the original Classic Java virtual machine 1 . JVMPI uses object IDs, not JNI object types, requiring agent libraries to manage them and convert them when using JNI. It also included some binary dump formats that you won't see in JVMTI. Certain Garbage Collectors would not work with JVMPI, and use of JVMPI did have a performance impact on the JVM. It has been deprecated in Java 2 SDK version 1.5.0, and the current plan of record is to remove it from Java 2 SDK version 1.6.0.
 
The JVMDI was also a native interface that was introduced in Java 2 SDK version 1.1. It is mentioned here because along with JVMPI, JVMDI will also be targeted for removal in Java 2 SDK version 1.6.0. The transition from JVMDI to JVMTI is considerably easier than from JVMPI, and we will not be covering the JVMDI transition in this document. The use of JVMDI in the Java Debug Wire Protocol ( JDWP) in the Java Platform Debugging Architecture ( JPDA) has been replaced with JVMTI in the Java 2 SDK version 1.5.0.
 
Transition Issues for JVMPI Agents
Interfaces

In general, you will find that JVMTI has many more interfaces than JVMPI. Here is a map from JVMPI to JVMTI, or some notes on why there is no mapping.

 
JVMPI
JVMTI
Notes
EnableEvent SetEventNotificationMode Use jvmtiEventMode == JVMTI_ENABLE
DisableEvent SetEventNotificationMode Use jvmtiEventMode == JVMTI_DISABLE
RequestEvent   No JVMTI equivalent. This was used for creation of JVMPI Heap, Monitor, or Object event dumps, which are not created by JVMTI. JVMTI instead uses function calls to extract this information. JVMTI GenerateEvents is not the same thing, be careful.
GetCallTrace GetStackTrace JVMTI provides bytecode offsets, not line numbers.
ProfilerExit   No direct JVMTI equivalent. It wasn't clear that a special JVMTI Exit function was necessary. Use of the standard exit() function or JNI FatalError seemed to cover this.
RawMonitorCreate CreateRawMonitor Basic raw monitor usage is the same, just a typedef name change: JVMPI_RawMonitor -> jrawMonitorID
RawMonitorEnter RawMonitorEnter  
RawMonitorExit RawMonitorExit  
RawMonitorWait RawMonitorWait  
RawMonitorNotifyAll RawMonitorNotifyAll  
RawMonitorDestroy DestroyRawMonitor  
GetCurrentThreadCpuTime GetCurrentThreadCpuTime  
SuspendThread SuspendThread For all thread interfaces, JVMTI accepts a jthread, not a JNIEnv*, and NULL can be used as the current thread.
SuspendThreadList SuspendThreadList  
ResumeThread ResumeThread  
ResumeThreadList ResumeThreadList  
GetThreadStatus GetThreadState The JVMTI thread state contains more detail on the thread state.
ThreadHasRun   No direct JVMTI equivalent. Sampling the current frame location and the GetCurrentThreadCpuTime may provide a replacement for this feature.
CreateSystemThread RunAgentThread JVMTI requires the caller to provide a freshly created java.lang.Thread object.
SetThreadLocalStorage SetThreadLocalStorage  
GetThreadLocalStorage GetThreadLocalStorage  
DisableGC   No JVMTI equivalent. JVMPI required GC to be disabled for some operations, JVMTI does not.
EnableGC   No JVMTI equivalent. JVMPI required GC to be disabled for some operations, JVMTI does not.
RunGC ForceGarbageCollection  
GetThreadObject   No JVMTI equivalent, not needed. JVMTI uses jobject not object IDs.
GetMethodClass GetMethodDeclaringClass  
jobjectID2jobject   No JVMTI equivalent, not needed. JVMTI uses jobject not object IDs.
jobject2jobjectI   No JVMTI equivalent, not needed. JVMTI uses jobject not object IDs.

 
 
Events

The JVMPI event callback mechanism consisted of one callback function that was passed a large C struct/union of information. In JVMTI, each event has its own callback function with a unique function prototype for that event.

 
JVMPI Event
JVMTI Event
Notes
JVMPI_EVENT_ARENA_DELETE   No JVMTI equivalent. Never implemented in the Reference Implementation of JVMPI. Depends too much on the specific Garbage Collection being used.
JVMPI_EVENT_ARENA_NEW   No JVMTI equivalent. Never implemented in the Reference Implementation of JVMPI. Depends too much on the specific Garbage Collection being used.
JVMPI_EVENT_CLASS_LOAD JVMTI_EVENT_CLASS_LOAD  
JVMPI_EVENT_CLASS_LOAD_HOOK JVMTI_EVENT_CLASS_FILE_LOAD_HOOK  
JVMPI_EVENT_CLASS_UNLOAD   No direct JVMTI equivalent. Class unloads can be detected by a query of all classes loaded, and comparing it to a previously saved list. Or can be detected by using SetTag and ObjectFree events.
JVMPI_EVENT_COMPILED_METHOD_LOAD JVMTI_EVENT_COMPILED_METHOD_LOAD  
JVMPI_EVENT_COMPILED_METHOD_UNLOAD JVMTI_EVENT_COMPILED_METHOD_UNLOAD  
JVMPI_EVENT_DATA_DUMP_REQUEST JVMTI_EVENT_DATA_DUMP_REQUEST  
JVMPI_EVENT_DATA_RESET_REQUEST   No JVMTI equivalent. In all known JVMPI implementations it was redundant with JVMPI_EVENT_DATA_DUMP_REQUEST. It was determined that this event was not necessary in JVMTI.
JVMPI_EVENT_GC_FINISH JVMTI_EVENT_GARBAGE_COLLECTION_FINISH  
JVMPI_EVENT_GC_START JVMTI_EVENT_GARBAGE_COLLECTION_START  
JVMPI_EVENT_HEAP_DUMP   No direct JVMTI equivalent. See JVMTI Heap Iterate.
JVMPI_EVENT_JNI_GLOBALREF_ALLOC   No direct JVMTI equivalent. See JVMTI SetJNIFunctionTable.
JVMPI_EVENT_JNI_GLOBALREF_FREE   No direct JVMTI equivalent. See JVMTI SetJNIFunctionTable.
JVMPI_EVENT_JNI_WEAK_GLOBALREF_ALLOC   No direct JVMTI equivalent. See JVMTI SetJNIFunctionTable.
JVMPI_EVENT_JNI_WEAK_GLOBALREF_FREE   No direct JVMTI equivalent. See JVMTI SetJNIFunctionTable.
JVMPI_EVENT_JVM_INIT_DONE JVMTI_EVENT_VM_INIT  
JVMPI_EVENT_JVM_SHUT_DOWN JVMTI_EVENT_VM_DEATH  
JVMPI_EVENT_METHOD_ENTRY JVMTI_EVENT_METHOD_ENTRY JVMTI version not equivalent to JVMPI version and should not be used where performance is an issue. See BCI.
JVMPI_EVENT_METHOD_ENTRY2   No direct JVMTI equivalent. With JVMTI, you would need to use GetLocalVariable on "this".
JVMPI_EVENT_METHOD_EXIT JVMTI_EVENT_METHOD_EXIT JVMTI version not equivalent to JVMPI version and should not be used where performance is an issue. See BCI.
JVMPI_EVENT_MONITOR_CONTENDED_ENTER JVMTI_EVENT_MONITOR_CONTENDED_ENTER  
JVMPI_EVENT_MONITOR_CONTENDED_ENTERED JVMTI_EVENT_MONITOR_CONTENDED_ENTERED  
JVMPI_EVENT_MONITOR_CONTENDED_EXIT   No JVMTI equivalent.
JVMPI_EVENT_MONITOR_DUMP   No direct JVMTI equivalent. See JVMTI Thread and Monitor.
JVMPI_EVENT_MONITOR_WAIT JVMTI_EVENT_MONITOR_WAIT  
JVMPI_EVENT_MONITOR_WAITED JVMTI_EVENT_MONITOR_WAITED  
JVMPI_EVENT_OBJECT_ALLOC   No direct JVMTI equivalent. See BCI.
JVMPI_EVENT_OBJECT_DUMP   No direct JVMTI equivalent. See BCI.
JVMPI_EVENT_OBJECT_FREE JVMTI_EVENT_OBJECT_FREE Requires that the object be tagged (see JVMTI SetTag) which likely requires BCI.
JVMPI_EVENT_OBJECT_MOVE   No JVMTI equivalent, not needed. The object type jobject does not move.
JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTER   No JVMTI equivalent.
JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTERED   No JVMTI equivalent.
JVMPI_EVENT_RAW_MONITOR_CONTENDED_EXIT   No JVMTI equivalent.
JVMPI_EVENT_THREAD_END JVMTI_EVENT_THREAD_END  
JVMPI_EVENT_THREAD_START JVMTI_EVENT_THREAD_START  
JVMPI_EVENT_INSTRUCTION_START   No direct JVMTI equivalent. See JVMTI_EVENT_SINGLE_STEP for a possible replacement. Also see BCI.

 
 
JNI and Local/Global References
The JNI references returned by JVMTI are all JNI Local References. That means you may need to call JNI functions to create Global or Weak Global references to objects if you need to save or preserve these objects between JVMTI event callbacks. In general, it is best to avoid creating JNI Global references unless absolutely necessary, since they basically prevent the object from being garbage collected, along with all objects that this object refers to. The JNI documents cover this topic fairly well and I won't go into further detail here. This is probably the only major porting issue for JVMDI to JVMTI conversion. But for JVMPI, just converting to JNI reference types from JVMPI object ID form can be significant.
 
Heap Dumps, Heap Iteration Functions, Tagging Objects and Byte Code Insertion ( BCI)

Probably the most significant difference between JVMPI and JVMTI is the handling of the Java Heap. With JVMPI, a request would be made for a Heap Dump, and the resulting JVMPI event would contain a single large block of data in a specific format that would need to be parsed by the agent library. With JVMTI, there are some basic Heap Iteration functions to traverse the Heap in various general ways, but to track all object allocations in detail, the objects need to be captured at the allocation site and tagged. The tags are carried with the object and can be used in various ways. In some situations like the JVMTI_EVENT_OBJECT_FREE event, the object must be tagged to get the event. Tagging objects usually requires some degree of BCI, which JVMTI supports in various ways. BCI involves inserting additional bytecodes into methods to instrument the method. BCI can be accomplished by displacing the original classes through the CLASSPATH with the instrumented classes, changing the class image at class load time with the JVMTI_EVENT_CLASS_FILE_LOAD_HOOK event, or calling JVMTI RedefineClasses to displace a class image that was previously loaded. Note that JVMTI just provides the ability to do BCI, it does not provide BCI support code for you. See the demo/jvmti library java_crw_demo.

The JVMPI event JVMPI_EVENT_OBJECT_ALLOC is not available in JVMTI. Use of BCI, SetTag, and the event JVMTI_EVENT_OBJECT_FREE can be used to obtain the same functionality.
 
Expected JVM Performance Differences
Just using JVMPI triggers a different JVM bytecode interpretation loop, changing the overall application performance. With JVMTI, adding certain capabilities can cause a change in the JVM and the application performance. However, most JVMTI usage incurs little or no performance penalty. Doing BCI has the potential to drastically change overall application performance, depending on the injected bytecodes and what they cause to happen in the application.
 
Differences in Agent and VM Initialization

With JVMPI, the first agent library code to be executed was the JVM_OnLoad extern, with JVMTI it is the Agent_OnLoad extern. The initial JVMPI event is JVMPI_EVENT_JVM_INIT_DONE, for JVMTI the equivalent event is JVMTI_EVENT_VM_INIT. However, JVMTI also provides an earlier event, JVMTI_EVENT_VM_START, that represents the earliest possible time where a JNI call can be made. JVMTI has distinct phases (see jvmtiPhase) and all interfaces have specifications as to which phases they can be called in. With JVMTI, it is important that the needed JVMTI capabilities be added to the JVMTI environment ( jvmtiEnv*) during the on load phase (inside Agent_OnLoad). JVMTI also provides an equivalent Agent_OnUnload for when the agent library is actually unloaded from the VM process.

A special note here for people attempting to create a single shared library that is composed of both JVMPI or JVMDI usage along with JVMTI. A JVMTI agent library can be loaded into the JVM with the -agentlib or -agentpath options, but can also be loaded in with the older -Xrun option. The version 1.5.0 JVM, when given the -Xrun option, first looks for the Agent_OnLoad extern in the library, and if seen, gives JVMTI priority, only calling Agent_OnLoad and ignoring any JVM_OnLoad extern, effectively treating this -Xrun option usage as if it was an -agentlib option. Since the older JVM implementations only accept the -Xrun option, and will only be looking for the JVM_OnLoad extern, a single shared library containing both the Agent_OnLoad and the JVM_OnLoad externs could serve as double agents when using the -Xrun option, appearing as a JVMPI or JVMDI agent library to the older JVM implementations, and as a JVMTI agent library to the version 1.5.0 JVM implementation. Whether this is a good idea or not is left as an exercise to the reader.

In addition, another difference to note is that Agent_OnLoad will be called earlier in the JVM startup than JVM_OnLoad. The JVMTI phases protect you from calls happening too early inside Agent_OnLoad, but this could be an important difference for some users.

 
Some Basics on JVMTI Usage
All code examples in this document have been taken from the available JVMTI demo agents provided in the Java 2 SDK version 1.5.0 (look in the demo/jvmti directory).
 
Startup

Agents are loaded into a JVM through one of the java options -agentlib: agent[= options] or –agentpath: path[= options] . In addition, the older java option –Xrun can be used (for example, –Xrun agent [:options]).

The Agent_OnLoad extern is the very first agent library code to be executed. Inside Agent_OnLoad the JVMTI environment is requested from the JVM, the necessary JVMTI capabilities are added to that environment, and the initial JVMTI event callbacks are setup and enabled. Some of the JVMTI setup can be done here, or in the JVMTI_EVENT_VM_INIT event callback, but parts of JVMTI cannot be called until the JVMTI_EVENT_VM_INIT event, so JVMTI calls inside Agent_OnLoad are limited to the "onload" phase (see the discussion of JVMTI phases below).

JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
jint rc;
jvmtiCapabilities capabilities;
jvmtiEventCallbacks callbacks;
jvmtiEnv *jvmti;

rc = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION);
if (rc != JNI_OK) {
fprintf(stderr, "ERROR: Unable to create jvmtiEnv, GetEnv failed, error=%d\n", rc);
return -1;
}
(*jvmti)->GetCapabilities(jvmti, &capabilities);
capabilities.can_tag_objects = 1;
capabilities.can_generate_garbage_collection_events = 1;
(*jvmti)->AddCapabilities(jvmti, &capabilities);
memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMInit = &vmInit;
callbacks.VMDeath = &vmDeath;
callbacks.DataDumpRequest = &dataDumpRequest;
(*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL);
return 0;
}

Note that all of the JVMTI error return handling and checking code has been left out of the above example. Do not do this in real life; those error returns are always worth checking.

The JVMTI capabilities are best added very early, depending on the implementation. The available capabilities and the ability to add capabilities may be restricted to particular JVMTI phases. The phases ( jvmtiPhase) represent periods of time that the JVMTI environment exists. The key boundaries are: Agent_OnLoad, JVMTI_EVENT_VM_START, JVMTI_EVENT_VM_INIT, and JVMTI_EVENT_VM_DEATH.

The typical vmInit function might look like:

static void JNICALL
vmInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread)
{
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, NULL);
 
}

Note that in Agent_OnLoad, the callback for the JVMTI_EVENT_DATA_DUMP_REQUEST (Data Dump Request) was already set, we just enabled this event inside the vmInit function.

 
Interfaces

JVMTI in combination with JNI provides just about everything needed. It is recommended that you browse the JVMTI specification and also look again at all the possible JNI functions that can be used inside a JVMTI agent library. All JVMTI event callbacks get both the jvmtiEnv* pointer and also the JNIEnv* for the current thread, enabling easy JVMTI and JNI access. The interfaces can easily be called from C or C++, but the calls take on a slightly different appearance in the two languages:

  • C++
    jvmtiError err = jvmti->SetTag(object, tag);
  •  
  • C
    jvmtiError err = (*jvmti)->SetTag(jvmti, object, tag);
The #include file "jvmti.h" works for both languages, and both use full prototypes in all cases, so that as long as you avoid needless casts on the addresses of functions, the compilers should find any missing arguments or incorrect type usage. See the demo/jvmti/waiters demo for an example C++ agent library. All of JVMTI will return an error code. It is important that you check these error codes, even though the examples in this document don't.
 
Event Callbacks
Once you have set up the event callback, and enabled the events, the callback functions will get called from the JVM. Keep in mind that most of these callback functions need to be completely MT-safe (some such as the JVMTI_EVENT_VM_INIT will only be called once per VM initialization and you have a little flexibility knowing that it's the only active thread), so that means access to any static or extern data needs to be carefully handled, and any native system functions also need to be MT-safe. Ideally, all your C or C++ functions should be designed in a re-entrant style, and static or extern data should be avoided. The simplest approach to protecting static or global data (as seen in most of the JVMTI demos) is to create a single raw monitor in Agent_OnLoad or vmInit, and use that raw monitor in all the callbacks to assure that only one callback is active at any given point in time (creates a critical section in the native code). This is also the approach that can create performance bottlenecks in the application using the agent library, by preventing threads from executing at the same time. Often times, a per-thread raw monitor, or multiple raw monitors, will be a better performance solution, but may also become a correctness issue with regards to deadlocks if you aren't careful on the nesting orders when parts of the code need both raw monitors.
 
Byte Code Insertion

The ability to change the bytecodes of a class has existed in both JVMPI by way of the event JVMPI_EVENT_CLASS_LOAD_HOOK, and in JVMDI by way of RedefineClasses. In fact, by changing the classpath or using class loaders, the same BCI ability has always existed in some basic form. JVMTI just makes all of these possible. It is important to clarify that JVMTI does not actually do the BCI, just allows for it to happen. BCI can be done with various third party or open source classfile transformation libraries or tools. However, in this document I will treat BCI as a subset of what can be done by a class transformation library, so my definition of BCI limits the class transformation to just the addition of bytecodes and constant pool entries, and the necessary changes to any attributes that refer to the bytecode index values or the constant pool index values. Furthermore, it is intended that this BCI operation does not change the basic nature of the classfile's methods. In other words, you can't change the methods to do something functionally different, and you can't add methods or fields to the classfile. The intent with BCI is to insert bytecodes to understand the normal behavior of a class and the methods of that class.

As an example for the Java 2 SDK, the basic hprof ability (that has historically been provided with the Java 2 SDK since version 1.1) was converted from JVMPI to JVMTI. This is the new hprof provided in version 1.5.0, using JVMTI and BCI, and the complete source to this hprof agent, is available in the demo/jvmti directory, along with a fairly detailed HTML user manual. In addition, there is a smaller and simpler demo called heapViewer that should provide a basic understanding of how the JVMTI_EVENT_CLASS_FILE_LOAD_HOOK event works, which is also used by hprof.

Several of the demo/jvmti agents, including hprof and heapViewer, use a small shared demo library called java_crw_demo that is delivered as part of the Java 2 SDK version 1.5.0. The source to this library is also provided in the demo/jvmti directory. The java_crw_demo native library is a primitive classfile transformation library that will insert bytescodes at selected and limited locations in methods, returning a new classfile image. It is important that you understand the classfile layout as described in the Java Virtual Machine Specification. Some of the common issues you may encounter doing classfile transformations are:

  • Additions to the constant pool will be needed. It is easiest if these are added at the end of the constant pool. Don't forget to set the constant pool count correctly in the classfile header. If you do change the constant pool order, watch out for the ldc bytecodes, some may need to change to ldc_w bytecodes, and any attribute that uses a constant pool index will need to change, e.g., " InnerClasses".
  •  
  • Adding bytecodes can cause some of the bytecodes that follow to need different bytecodes to deal with changing ranges, e.g., a jsr vs. a jsr_w instruction. Special care needs to be taken when re-constructing the new Code attribute that all the bytecodes, both inserted and original, are using the correct wide and ' *_w' bytecodes.
  •  
  • Changes to the bytecodes means that the offsets in the " Exceptions", " LineNumberTable", " LocalVariableTable", and (new for version 1.5.0) " LocalVariableTypeTable" tables will be invalid. You will need to adjust all these offsets.
  •  
  • If the inserted bytecode causes or could cause the maximum stack size to increase, the " Code" attribute will need the max_stack field adjusted along with the code_length . If you add local variables, you will also need to adjust max_locals .
  •  
  • Insertion of bytecodes at the very beginning needs to be done carefully, consider a jump to offset 0 in the original bytecodes. You need to decide if the inserted bytecodes will be executed once on entry, or also when the method does a jump to offset 0.
  •  
  • Insertion before return bytecodes can also be tricky. If in the original bytecodes this return bytecode is a target of a jump, do you want the inserted bytecodes to be executed?
  •  
  • The special case of the new bytecode is a less-than-obvious problem. Objects that have not been initialized cannot be passed to ANY Java methods, so doing the obvious injection of a dup and an invokestatic after the new bytecode will not work when bytecode verification is on. This object must be initialized first. So if you wish to capture newly-allocated objects, the best place to catch them is in the java.lang.Object.<init> method. Once the object makes it here, you can pass the object around (or of course you could run Java with the verifier off, but that generally isn't a good idea). The newarray bytecode doesn't have this problem, and in fact the only way to capture these objects is by inserting bytecodes immediately after the newarray bytecode (don't forget the anewarray and multianewarray bytecodes).
  •  
  • It is best to insert the fewest bytecodes possible, and in java_crw_demo, the insertion is limited to pushing a few items on the stack, and making a static method call to a class found in the boot classpath. This so-called tracker class and tracker methods contain Java code that, in our demos, grab the current Thread with a call to java.lang.Thread.currentThread, and pass all the arguments, plus the current thread to a native method belonging to the class. The agent will have registered the natives for this class and those native functions are actually static functions inside the agent library.
  •  
  • Any bytecode insertion needs to be careful of the state of the VM when it is called, e.g., has the VM started, or has the VM been initialized. The downside to the JNI call in the Tracker class is that you can't make the JNI call unless the VM is started and the natives have been registered, and the downside to calling currentThread is that before the VM is initialized, this thread reference could be null.
  •  
  • Once past the VM initialization, any JNI usage and all the native code in the agent library needs to watch out for JVMTI_EVENT_VM_DEATH, making sure the code can recover cleanly.
  •  
  • You may wish to be selective in what classes or methods you apply BCI to, depending on what information you are after. It is always best to limit the intrusion of BCI on the application.

As an example, consider the following Java code:

public class Demo {
private Demo[] newDemo() {
Demo d[] = new Demo[3];
return d;
}
}


The hprof agent library does BCI in 4 different places, method entry and method exit for the cpu=times option, and java.lang.Object.<init> and newarray bytecodes for the heap=* option. What hprof (or technically java_crw_demo gets as input) looks basically like this (used javap -c -l -private to dump the class file):

Compiled from "Demo.java"
public class Demo extends java.lang.Object{
public Demo();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LDemo;

private Demo[] newDemo();
Code:
0: iconst_3
1: anewarray #2; //class Demo
4: astore_1
5: aload_1
6: areturn

LineNumberTable:
line 3: 0
line 4: 5
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this LDemo;
5 2 1 d [LDemo;


}

After hprof or java_crw_demo gets done with the class, the class would look like:


Compiled from "Demo.java"
public class Demo extends java.lang.Object{
public Demo();
Code:
0: ldc #20; //int 267309056
2: iconst_0
3: invokestatic #34; //Method sun/tools/hprof/Tracker.CallSite:(II)V
6: aload_0
7: invokespecial #1; //Method java/lang/Object."<init>":()V
10: ldc #20; //int 267309056
12: iconst_0
13: invokestatic #38; //Method sun/tools/hprof/Tracker.ReturnSite:(II)V
16: return

LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 17 0 this LDemo;

private Demo[] newDemo();
Code:
0: ldc #20; //int 267309056
2: iconst_1
3: invokestatic #34; //Method sun/tools/hprof/Tracker.CallSite:(II)V
6: iconst_3
7: anewarray #2; //class Demo
10: dup
11: invokestatic #30; //Method sun/tools/hprof/Tracker.NewArray:(Ljava/lang/Object;)V
14: astore_1
15: aload_1
16: ldc #20; //int 267309056
18: iconst_1
19: invokestatic #38; //Method sun/tools/hprof/Tracker.ReturnSite:(II)V
22: areturn

LineNumberTable:
line 3: 0
line 4: 15
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this LDemo;
15 8 1 d [LDemo;

}

With appropriate changes to other fields in the class file not shown here ( max_stack, constant pool, etc.). The 267309056 number is the class number assigned to this class by the caller of the java_crw_demo library, the second number is the method index for the method getting the insertion. Note the adjustments made to the LineNumberTable and LocalVariableTable.

At runtime, the methods in sun/tools/hprof/Tracker will call native methods that reside inside the agent library, where JVMTI can be used to sample the stack or in general track this BCI event you have created.

 
Object Tagging and Heap Iteration

JVMTI allows you to attach a tag of type jlong (64 bit value) to any Java object in the Heap. This tag stays with the Java object, including movements caused by compaction after Garbage Collection. JVMTI's object-free event provides you that jlong tag value instead of an object reference, and JVMTI Heap Iterate also provides these tag values as a form of object identification. The tags can be used to uniquely identify an object, or provide a way to categorize objects. Capturing the objects in order to tag them often involves using BCI, but doesn't require it.

A simple non-BCI heap dump can be obtained by tagging all the loaded classes ( jclass objects or java.lang.Class) and then using JVMTI IterateOverHeap:

static jvmtiIterationControl JNICALL
heapObject(jlong class_tag, jlong size, jlong* tag_ptr, void* user_data)
{
if ( class_tag != (jlong)0 ) {
ClassDetails *d;
d = (ClassDetails*)(void*)(ptrdiff_t)class_tag;
d->count++;
d->space += size;
}
return JVMTI_ITERATION_CONTINUE;
}

static void JNICALL
dataDumpRequest(jvmtiEnv *jvmti)
{
jclass *classes;
jint count;
jint i;
ClassDetails *details;

(*jvmti)->GetLoadedClasses(jvmti, &count, &classes);
details = (ClassDetails*)calloc(sizeof(ClassDetails), count);
for ( i = 0 ; i < count ; i++ ) {
char *sig;
(*jvmti)->GetClassSignature(jvmti, classes[i], &sig, NULL);
details[i].signature = strdup(sig);
(*jvmti)->Deallocate(jvmti, sig);
(*jvmti)->SetTag(jvmti, classes[i], (jlong)(ptrdiff_t)(void*)(&details[i]));
}
(*jvmti)->IterateOverHeap(jvmti, JVMTI_HEAP_OBJECT_EITHER, &heapObject, NULL);
}

Note that much of the error handling and checking code has been left out of the above example, you should refer to the complete source of this demo for complete details. The above example code does not tell you where these objects were allocated, and not all objects will be accounted for (primitive classes are not included), but it does provide a quick and easy way to find statistics on the space used by the various objects in the heap. Some common tips around tags and heap iterations:

  • When using a tag that is a pointer, use a cast to ptrdiff_t(Standard C typedef for an integer that holds a pointer difference) to avoid compiler warnings and errors. Never use int or long, you'll find out that int and long are not always big enough to hold a pointer, truncating your address, where ptrdiff_t will always be big enough to hold all the bits of a pointer.
  •  
  • Remember that any pointer used as a tag is native memory that cannot move, or if moved, the tag is invalid.
  •  
  • The heap iteration callbacks can execute with a different thread than the iterate call, so holding a raw monitor around the iterate call is a guarantee of a deadlock if the callbacks also need that raw monitor.
  •  
  • The iterate callbacks allow you to change or set the tags on objects, so they also serve as a quick way to tag groups of objects, or remove tags.
  •  
  • Don't forget the other objects not allocated in bytecodes: track JNI allocations using JVMTI JNI function interception, and track misc JVM allocations with the JVMTI_EVENT_VM_OBJECT_ALLOC event.
 
General JVMTI and Agent Library Advice
  • Use GetCurrentThreadCpuTime over GetThreadCpuTime. Access to the current thread data will almost always be faster than accessing thread data from another thread.
  •  
  • When doing BCI, be careful to avoid infinite recursion (the BCI bytecodes calling the BCI code, or the BCI code making some other Java call which results in calls to the BCI code).
  •  
  • Be very careful with RawMonitorEnter calls inside BCI code. Holding locks in BCI code can destroy performance on multiple-CPU machines.
  •  
  • Operations on thread lists will naturally be more efficient than cycling over the threads yourself, e.g., use GetThreadListStackTraces instead of looping over GetStackTrace.
  •  
  • Isolate any JNI object references ( jobject, jstring, jclass, or jthread) that need to be saved. These will need JNI Global or Weak references created for them, and you will need to manage these references, using JNI to delete them when they are no longer needed.
  •  
  • Check ALL the jvmtiError returns, you'd be surprised how important it is to find these non- JVMTI_ERROR_NONE error returns early in the agent library execution.
  •  
  • The JVMTI_ERROR_WRONG_PHASE errors are your friends, they are likely telling you that the agent library has a thread race condition. The older interfaces JVMPI and JVMDI did not have this feature, and with JVMTI having documented and specified phases where everything can be called, these PHASE errors can help you track down synchronization errors during agent startup and shutdown.
  •  
  • Watch out for JVMTI memory allocations by some of the interfaces. It is the caller's responsibility to deallocate this space with JVMTI Deallocate.
  •  
  • You can use JVMTI Allocate to allocate all your agent memory needs, or you can use the system malloc functions. If you choose to use malloc or something other than JVMTI, be very careful not to mix these up. Using multiple memory allocation schemes in your native code can be error-prone.
  •  
  • On Linux and Solaris, use version scripts or mapfiles when building your shared library, to limit the externs available to Agent_OnLoad and Agent_OnUnload. On Windows only, export the Agent_OnLoad and Agent_OnUnload externs. In general, this is just a safe way to build your shared library.
  •  
  • See the main demo/jvmti/index.html page for help in selecting the right C and C++ compiler options.
  •  
  • Consider some kind of tracing or logging option in your agent. Something as simple as this might prove useful to you:
    #define LOG(a) ( gdata->logflags != 0 ? printf a : 0 )
    ...
    LOG(("Entered callback %s\n", VMStart));
  •  
  • Consider a pause=y option that will pause the process at Agent_OnLoad option parsing time, allowing for a debugger to attach to the process. Have the pause=y option print out a message containing the PID (see getpid).
  •  
  • JVMTI does provide some tracing options in the VM. These options may not be available in all the VM's and this is not an officially supported option. The syntax is ' java -XX:TraceJVMTI= desc {, desc } ... ' where each desc is composed of 3 fields (no blanks) ' domain action kind '. The domain field is one of name (a particular function or event name), all (all functions and events), func (all major functions), allfunc (all functions), event (all events), or ec (event controller). The action field is '+' to add or '-'to remove. The kind field is 'i' (input params), 'e' (error returns), 'o' (output), 't' (event triggered a.k.a. posted), or 's' (event sent). An example: java-XX:TraceJVMTI=ec+,GetCallerFrame+ie,Breakpoint+s ...
  •  
  • Don't be afraid to use the java options like -Xcheck:jni
 
Summary

How easy the transition from JVMPI to JVMTI is really depends on the complexity of the specific agent library, how much of JVMPI was used, and using JVMTI in such a way that performance of the target application doesn't suffer. Some of the conversion will be fairly straightforward, but other parts may take some time and even require several iterations over the design. Don't underestimate this effort.

JVMTI is part of Java Specification Request 163 ( JSR-163) of the Java Community Process ( JCP).

All the examples in this document have been taken from operating JVMTI demo agents available with the Java 2 SDK version 1.5.0. To see the latest JVMTI demos, just point your browser to the demo/jvmti directory of your Java 2 SDK installation and you should have access to all the demo documents plus the complete source of all the JVMTI demos in the file demo/jvmti/src.zip. Downloads are available at the J2SE 1.5.0 Beta 2 downloads page.

 
About the Author
Kelly O'Hair is a Senior Staff Engineer in Sun Microsystems' Java Serviceability group. He has been at Sun for over 11 years having worked on dbx, Java WorkShop, fastjavac, Java on Solaris, native compiler Dwarf debug format generation, and the Native Connector Tool that allows Java easy access to native shared libraries.

More recently Kelly has been working with the JVMTI interface, making sure this new interface is well tested, documented, and easy to use. He holds an M.S. degree in Computing Science from University of California, Davis and a B.A. in Mathematics from California State University, Long Beach.
 


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.