google-site-verification: google61fe8ba583a51912.html
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