|
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
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.
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_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.
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.
|
|