JVMTI is a native programming interface that provides tools with both a way to inspect the state and control the execution of applications running in the JVM.
1. JVMTI Overview and Architecture
// jvmti_demo.h - Basic JVMTI header includes #include <jvmti.h> #include <jni.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // JVMTI is a two-way interface: // 1. JVM → Agent (via events) // 2. Agent → JVM (via functions)
2. Basic JVMTI Agent Structure
// basic_agent.c - Basic JVMTI agent structure
#include <jvmti.h>
#include <jni.h>
// Global jvmti environment
static jvmtiEnv *jvmti = NULL;
// Agent initialization
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
printf("JVMTI Agent Loading...\n");
jint result = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1_2);
if (result != JNI_OK || jvmti == NULL) {
printf("ERROR: Unable to access JVMTI!\n");
return JNI_ERR;
}
// Set capabilities
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
// Request basic capabilities
capabilities.can_generate_all_class_hook_events = 1;
capabilities.can_get_bytecodes = 1;
capabilities.can_get_synthetic_attribute = 1;
capabilities.can_tag_objects = 1;
jvmtiError error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
if (error != JVMTI_ERROR_NONE) {
printf("ERROR: Unable to set capabilities!\n");
return JNI_ERR;
}
// Set callbacks
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassLoad = &class_load_handler;
callbacks.ClassPrepare = &class_prepare_handler;
callbacks.VMInit = &vm_init_handler;
callbacks.VMDeath = &vm_death_handler;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
if (error != JVMTI_ERROR_NONE) {
printf("ERROR: Unable to set callbacks!\n");
return JNI_ERR;
}
// Enable events
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL);
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL);
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL);
printf("JVMTI Agent Loaded Successfully\n");
return JNI_OK;
}
// Agent shutdown
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
printf("JVMTI Agent Unloading...\n");
}
3. Event Handlers Implementation
// event_handlers.c - JVMTI event handlers
#include <jvmti.h>
#include <jni.h>
// VM Initialization event
void JNICALL vm_init_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) {
printf("VM Initialized\n");
// Now we can safely call JNI functions
jclass thread_class = (*jni_env)->FindClass(jni_env, "java/lang/Thread");
if (thread_class != NULL) {
printf("Found Thread class\n");
}
}
// VM Death event
void JNICALL vm_death_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env) {
printf("VM Shutting Down\n");
}
// Class Load event
void JNICALL class_load_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
jthread thread, jclass klass) {
char* class_name;
jvmtiError error = (*jvmti_env)->GetClassSignature(jvmti_env, klass, &class_name, NULL);
if (error == JVMTI_ERROR_NONE && class_name != NULL) {
printf("Class Loaded: %s\n", class_name);
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)class_name);
}
}
// Class Prepare event
void JNICALL class_prepare_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
jthread thread, jclass klass) {
char* class_name;
jvmtiError error = (*jvmti_env)->GetClassSignature(jvmti_env, klass, &class_name, NULL);
if (error == JVMTI_ERROR_NONE && class_name != NULL) {
printf("Class Prepared: %s\n", class_name);
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)class_name);
}
}
4. Method Instrumentation Agent
// method_agent.c - Method entry/exit instrumentation
#include <jvmti.h>
#include <jni.h>
static jvmtiEnv *jvmti = NULL;
// Method entry callback
void JNICALL method_entry_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
jthread thread, jmethodID method) {
char* method_name = NULL;
char* class_name = NULL;
char* method_signature = NULL;
jvmtiError error = (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name,
&method_signature, NULL);
if (error == JVMTI_ERROR_NONE) {
jclass declaring_class;
error = (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &declaring_class);
if (error == JVMTI_ERROR_NONE) {
error = (*jvmti_env)->GetClassSignature(jvmti_env, declaring_class, &class_name, NULL);
}
if (error == JVMTI_ERROR_NONE && class_name != NULL && method_name != NULL) {
printf("→ ENTER: %s.%s%s\n", class_name, method_name, method_signature);
}
if (method_name != NULL) {
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)method_name);
}
if (method_signature != NULL) {
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)method_signature);
}
if (class_name != NULL) {
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)class_name);
}
}
}
// Method exit callback
void JNICALL method_exit_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
jthread thread, jmethodID method,
jboolean was_popped_by_exception, jvalue return_value) {
char* method_name = NULL;
char* class_name = NULL;
jvmtiError error = (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name, NULL, NULL);
if (error == JVMTI_ERROR_NONE) {
jclass declaring_class;
error = (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &declaring_class);
if (error == JVMTI_ERROR_NONE) {
error = (*jvmti_env)->GetClassSignature(jvmti_env, declaring_class, &class_name, NULL);
}
if (error == JVMTI_ERROR_NONE && class_name != NULL && method_name != NULL) {
printf("← EXIT: %s.%s\n", class_name, method_name);
}
if (method_name != NULL) {
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)method_name);
}
if (class_name != NULL) {
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)class_name);
}
}
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jint result = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1_2);
if (result != JNI_OK) return JNI_ERR;
// Set capabilities for method tracing
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_generate_method_entry_events = 1;
capabilities.can_generate_method_exit_events = 1;
capabilities.can_get_method_name = 1;
(*jvmti)->AddCapabilities(jvmti, &capabilities);
// Set callbacks
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.MethodEntry = &method_entry_handler;
callbacks.MethodExit = &method_exit_handler;
(*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
// Enable method events
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL);
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, NULL);
return JNI_OK;
}
5. Java Test Application
// JVMTITestApplication.java - Test application for JVMTI agent
public class JVMTITestApplication {
public static void main(String[] args) {
System.out.println("JVMTI Test Application Started");
// Create some objects and call methods to trigger JVMTI events
JVMTITestApplication app = new JVMTITestApplication();
app.performOperations();
System.out.println("JVMTI Test Application Completed");
}
public void performOperations() {
// These method calls will be tracked by JVMTI agent
calculateSum(10, 20);
createObjects();
callStaticMethod();
}
private int calculateSum(int a, int b) {
System.out.println("Calculating sum: " + a + " + " + b);
return a + b;
}
private void createObjects() {
String text = "Hello JVMTI";
List<String> list = new ArrayList<>();
list.add(text);
System.out.println("Created objects: " + list);
}
private static void callStaticMethod() {
System.out.println("Static method called");
}
// Exception generation for testing
public void generateException() {
try {
throw new RuntimeException("Test exception for JVMTI");
} catch (Exception e) {
System.out.println("Exception caught: " + e.getMessage());
}
}
}
6. Object Tagging and Heap Analysis
// heap_analysis_agent.c - Object tagging and heap analysis
#include <jvmti.h>
#include <jni.h>
static jvmtiEnv *jvmti = NULL;
static jlong object_counter = 0;
// Object allocation callback
void JNICALL object_alloc_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
jthread thread, jobject object,
jclass object_klass, jlong size) {
// Tag the object with a unique ID
jlong tag = ++object_counter;
(*jvmti_env)->SetTag(jvmti_env, object, tag);
char* class_name = NULL;
jvmtiError error = (*jvmti_env)->GetClassSignature(jvmti_env, object_klass, &class_name, NULL);
if (error == JVMTI_ERROR_NONE && class_name != NULL) {
printf("Object Allocated: %s, Size: %ld, Tag: %ld\n", class_name, size, tag);
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)class_name);
}
}
// Heap iteration callback
jint JNICALL heap_iteration_callback(jlong class_tag, jlong size, jlong* tag_ptr,
jint length, void* user_data) {
if (*tag_ptr != 0) {
printf("Heap Object - Tag: %ld, Size: %ld\n", *tag_ptr, size);
}
return JVMTI_VISIT_OBJECTS;
}
// Perform heap walk
void perform_heap_walk() {
printf("=== Heap Walk Starting ===\n");
jvmtiError error = (*jvmti)->IterateOverHeap(jvmti, JVMTI_HEAP_OBJECT_TAGGED,
heap_iteration_callback, NULL);
if (error != JVMTI_ERROR_NONE) {
printf("Heap iteration failed: %d\n", error);
}
printf("=== Heap Walk Completed ===\n");
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jint result = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1_2);
if (result != JNI_OK) return JNI_ERR;
// Set capabilities for heap analysis
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_tag_objects = 1;
capabilities.can_generate_object_free_events = 1;
capabilities.can_generate_vm_object_alloc_events = 1;
(*jvmti)->AddCapabilities(jvmti, &capabilities);
// Set callbacks
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMObjectAlloc = &object_alloc_handler;
(*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
// Enable events
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
return JNI_OK;
}
7. Thread Monitoring Agent
// thread_agent.c - Thread lifecycle monitoring
#include <jvmti.h>
#include <jni.h>
static jvmtiEnv *jvmti = NULL;
// Thread start callback
void JNICALL thread_start_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) {
jvmtiThreadInfo thread_info;
jvmtiError error = (*jvmti_env)->GetThreadInfo(jvmti_env, thread, &thread_info);
if (error == JVMTI_ERROR_NONE && thread_info.name != NULL) {
printf("Thread Started: %s\n", thread_info.name);
// Clean up
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)thread_info.name);
if (thread_info.thread_group != NULL) {
(*jni_env)->DeleteLocalRef(jni_env, thread_info.thread_group);
}
if (thread_info.context_class_loader != NULL) {
(*jni_env)->DeleteLocalRef(jni_env, thread_info.context_class_loader);
}
}
}
// Thread end callback
void JNICALL thread_end_handler(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) {
jvmtiThreadInfo thread_info;
jvmtiError error = (*jvmti_env)->GetThreadInfo(jvmti_env, thread, &thread_info);
if (error == JVMTI_ERROR_NONE && thread_info.name != NULL) {
printf("Thread Ended: %s\n", thread_info.name);
// Clean up
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)thread_info.name);
if (thread_info.thread_group != NULL) {
(*jni_env)->DeleteLocalRef(jni_env, thread_info.thread_group);
}
if (thread_info.context_class_loader != NULL) {
(*jni_env)->DeleteLocalRef(jni_env, thread_info.context_class_loader);
}
}
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jint result = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1_2);
if (result != JNI_OK) return JNI_ERR;
// Set capabilities for thread monitoring
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_generate_thread_events = 1;
capabilities.can_get_thread_info = 1;
(*jvmti)->AddCapabilities(jvmti, &capabilities);
// Set callbacks
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ThreadStart = &thread_start_handler;
callbacks.ThreadEnd = &thread_end_handler;
(*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
// Enable thread events
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_START, NULL);
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, NULL);
return JNI_OK;
}
8. Building and Running JVMTI Agents
# Makefile for building JVMTI agents JAVA_HOME=/usr/lib/jvm/java-11-openjdk CC=gcc CFLAGS=-I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -fPIC -shared all: basic_agent.so method_agent.so heap_agent.so thread_agent.so basic_agent.so: basic_agent.c $(CC) $(CFLAGS) -o $@ $< method_agent.so: method_agent.c $(CC) $(CFLAGS) -o $@ $< heap_agent.so: heap_analysis_agent.c $(CC) $(CFLAGS) -o $@ $< thread_agent.so: thread_agent.c $(CC) $(CFLAGS) -o $@ $< clean: rm -f *.so test: java -agentpath:./basic_agent.so JVMTITestApplication java -agentpath:./method_agent.so JVMTITestApplication
9. Advanced JVMTI Features
// advanced_agent.c - Advanced JVMTI features
#include <jvmti.h>
#include <jni.h>
static jvmtiEnv *jvmti = NULL;
// Get all loaded classes
jclass* get_all_loaded_classes(jint* class_count) {
jclass* classes = NULL;
jvmtiError error = (*jvmti)->GetLoadedClasses(jvmti, class_count, &classes);
if (error != JVMTI_ERROR_NONE) {
printf("Failed to get loaded classes: %d\n", error);
return NULL;
}
return classes;
}
// Get class methods
void print_class_methods(jclass klass) {
jint method_count = 0;
jmethodID* methods = NULL;
jvmtiError error = (*jvmti)->GetClassMethods(jvmti, klass, &method_count, &methods);
if (error != JVMTI_ERROR_NONE) {
return;
}
char* class_name = NULL;
(*jvmti)->GetClassSignature(jvmti, klass, &class_name, NULL);
printf("Class %s has %d methods:\n", class_name, method_count);
for (int i = 0; i < method_count; i++) {
char* method_name = NULL;
char* method_signature = NULL;
error = (*jvmti)->GetMethodName(jvmti, methods[i], &method_name,
&method_signature, NULL);
if (error == JVMTI_ERROR_NONE) {
printf(" Method: %s%s\n", method_name, method_signature);
if (method_name != NULL) {
(*jvmti)->Deallocate(jvmti, (unsigned char*)method_name);
}
if (method_signature != NULL) {
(*jvmti)->Deallocate(jvmti, (unsigned char*)method_signature);
}
}
}
if (class_name != NULL) {
(*jvmti)->Deallocate(jvmti, (unsigned char*)class_name);
}
if (methods != NULL) {
(*jvmti)->Deallocate(jvmti, (unsigned char*)methods);
}
}
// Get object size
jlong get_object_size(jobject object) {
jlong size = 0;
jvmtiError error = (*jvmti)->GetObjectSize(jvmti, object, &size);
if (error != JVMTI_ERROR_NONE) {
printf("Failed to get object size: %d\n", error);
return -1;
}
return size;
}
// Force garbage collection
void force_garbage_collection() {
printf("Forcing garbage collection...\n");
jvmtiError error = (*jvmti)->ForceGarbageCollection(jvmti);
if (error != JVMTI_ERROR_NONE) {
printf("Failed to force GC: %d\n", error);
}
}
10. Java-side Agent Controller
// JVMTIAgentController.java - Java side controller for JVMTI agent
public class JVMTIAgentController {
// Native methods to interact with JVMTI agent
public native void performHeapWalk();
public native void forceGarbageCollection();
public native long getObjectSize(Object obj);
static {
// Load the native library
System.loadLibrary("advanced_agent");
}
public static void main(String[] args) {
JVMTIAgentController controller = new JVMTIAgentController();
// Demonstrate JVMTI features from Java side
controller.demoJVMTIFeatures();
}
public void demoJVMTIFeatures() {
System.out.println("Starting JVMTI Agent Controller Demo");
// Create some objects
List<String> stringList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
stringList.add("String " + i);
}
// Force GC via JVMTI
forceGarbageCollection();
// Get object size via JVMTI
long listSize = getObjectSize(stringList);
System.out.println("ArrayList size via JVMTI: " + listSize + " bytes");
// Perform heap walk
performHeapWalk();
System.out.println("JVMTI Agent Controller Demo Completed");
}
}
Key JVMTI Concepts
- Agents: Native libraries loaded by JVM
- Environment:
jvmtiEnvpointer for all operations - Capabilities: Features that must be explicitly requested
- Events: Notifications from JVM to agent
- Callbacks: Functions called when events occur
- Memory Management: Manual allocation/deallocation for strings and arrays
Common Use Cases
- Profiling and performance monitoring
- Debugging tools
- Code coverage tools
- Memory leak detection
- Security analysis
- Hot-swapping tools
JVMTI provides powerful low-level access to JVM internals, making it essential for building advanced development and monitoring tools.