Java Virtual Machine Tool Interface (JVMTI) in Java

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

  1. Agents: Native libraries loaded by JVM
  2. Environment: jvmtiEnv pointer for all operations
  3. Capabilities: Features that must be explicitly requested
  4. Events: Notifications from JVM to agent
  5. Callbacks: Functions called when events occur
  6. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper