The JVM Tool Interface (JVMTI) 是一个由JVM提供的用于开发针对Java程序开发与监控工具的编程接口,通过JVMTI接口(Native API)可以创建代理程序(Agent)以监视和控制 Java 应用程序,包括剖析、调试、监控、分析线程等。著名的JProfiler利用该项技术实现其对Java程序的运行态监控与性能分析。
值得注意的是JVMTI 并不一定在所有的 Java 虚拟机上都得到实现,目前Oracle(SUN)、IBM、OpenJDK以及一些开源的如 Apache Harmony DRLVM均对其进行了标准实现 。
由于JVMTI 是一套Native接口,因此使用 JVMTI 需要我们使用C/C++ 操纵JNI。
JVMTI程序通常通过Agent方式在JVM OnLoad phase(启动时)Start-Up,这个加载处于虚拟机初始化的早期,此时所有的 Java 类都未被初始化、所有的对象实例也都未被创建(也支持Live phase(运行时)的Start-Up)。在启动Java应用程序时,需加入以下JVM参数:
-agentlib:agent-lib-name=options
-agentpath:path-to-agent=options
JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会主动调用一些事件的回调接口,这些接口可以供开发者扩展自己的逻辑,实际上,对于JVMTI程序的Load过程可以遵循一种模板式的流程框架来完成:
(1)获取JVMTI环境(JVMTI Environment)
(2)注册所需的功能(Capabilities)
(3)注册事件通知(Event Notification)
(4)指定事件回调函数(Callback Method)
可以通过http://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html 和https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/ 进一步了解相关知识。
接下来,我们通过举例的方式,看看JVMTI能够为Java应用监测带来些什么?
我们首先编写一个简单的测试程序,用于展示我们举例中JVMTI Agent程序的功能,程序清单参考如下:
(1)Foo类
package org.xreztento.tester;
public class Foo {
public void bar() throws InterruptedException {
Thread.sleep(500);
System.out.println("Executing Foo.bar()");
}
public void baz() {
System.out.println("Executing Foo.baz()");
}
}
(2)Main类
package org.xreztento.tester;
public class Main {
public static void main(String[] args) throws InterruptedException{
Thread[] threads = new Thread[5];
Foo foo = new Foo();
foo.bar();
foo.baz();
for(int i = 0; i < threads.length; i++){
threads[i] = new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
for(Thread thread : threads){
thread.start();
thread.join();
}
}
}
我们将项目打包为tester.jar包,运行后输出结果如下:
使用 Instrumentation开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。
利用Instrumentation实现字节码增强是许多监控工具针对Java应用程序实现非“侵入式”监控技术的基础,JVMTI为其提供了Native接口,Java SE 5将其从本地代码中解放出来通过JavaAgent利用该本地接口实现了Java语言层级的接口。
我们这里先不讨论JavaAgent的上层实现方式,你可以直接利用JVMTI的Native接口完成class字节码加载时的字节码修改增强。在JVM加载class字节码时会产生一个JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件,你可以通过ClassFileLoadHook回调函数完成新字节码的定义工作。
需要特别注意的地方是,对字节码的修改需要开辟出一块新的内存空间,因此就像向操作系统申请内存空间使用如malloc一样,你需要使用(*jvmti)->Allocate在JVM内部申请出一块内存空间,参考如下代码:
#include
#include
#include
#include
void JNICALL callbackClassFileLoadHook(jvmtiEnv *jvmti,
JNIEnv *jni,
jclass class_being_redefined,
jobject loader,
const char *name,
jobject protection_domain,
jint class_data_len,
const unsigned char *class_data,
jint *new_class_data_len,
unsigned char **new_class_data) {
jvmtiError error;
if(strcmp(name, "org/xreztento/tester/Foo") == 0){
printf("loaded class name=%s\n ", name);
jint size = class_data_len;
*new_class_data_len = size;
//为新的class字节码数据区分配JVM内存
error = (*jvmti)->Allocate(jvmti, size, new_class_data);
memset(*new_class_data, 0, size);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
}
int i;
//遍历旧的字节码字符,将E字符修改为P
for(i = 0; i < size; i++){
if(class_data[i] == 'E'){
(*new_class_data)[i] = 'P';
} else {
(*new_class_data)[i] = class_data[i];
}
}
}
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){
jvmtiEnv *jvmti = NULL;
jvmtiError error;
//获取JVMTI environment
error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_1);
if (error != JNI_OK) {
fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
return JNI_ERR;
}
//注册功能
jvmtiCapabilities capabilities;
(void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
capabilities.can_generate_all_class_hook_events = 1 ;
capabilities.can_retransform_classes = 1 ;
capabilities.can_retransform_any_class = 1 ;
error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
return error;
}
//设置JVM事件回调
jvmtiEventCallbacks callbacks;
callbacks.ClassFileLoadHook = &callbackClassFileLoadHook;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");
return error;
}
//设置事件通知
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
return JNI_OK;
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
//do nothing
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm){
//do nothing
}
字节码增强的意义是你可以在原有执行方法内部添加自己的代码逻辑如一些方法执行期的性能监控逻辑,并且无需修改原程序Class文件,以完全无侵入式的代价完成对Java程序的监测。
我们的例子非常简单,将org/xreztento/tester/Foo类字节码中的E字符全部替换成P字符。
首先,编译一个JVMTI程序的静态库,参考以下脚本:
gcc agent.c -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.65-3.b17.el7.x86_64/include -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.65-3.b17.el7.x86_64/include/linux -shared -fPIC -o ./libtestagent.so
之后带agent运行我们的测试程序,如:
java -jar -agentpath:/root/jvmti/libtestagent.so tester.jar
运行后输出结果如下:
JVMTI提供了对每个Java方法执行的监控事件,当进入方法时触发JVMTI_EVENT_METHOD_ENTRY事件,方法执行完成触发JVMTI_EVENT_METHOD_EXIT,我们可以为两个事件编写回调函数完成对指定方法的执行性能数据的记录。
我们使用一个HashMap数据结构来对方法的执行过程进行保存,key为执行方法的线程标识+方法名,value记录Entry方法时的系统nanos。(本例中hashmap采用https://github.com/japeq/hashmap)
实现一个记录bar方法执行时的运行时间的逻辑,参考如下代码实现:
#include
#include
#include
#include
#include
#include
#include
#include "hashmap.h"
#define KEY_MAX_LENGTH 256
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#define container_of(ptr, type, member) \
((type *) ((char *) (ptr) - offsetof(type, member)))
struct method_t {
struct hash_node node;
int hash;
jlong start_time;
};
/**
hash算法
**/
static unsigned int
djb_hash(char* str, unsigned int len)
{
unsigned int hash = 5381;
unsigned int i = 0;
for(i = 0; i < len; str++, i++)
{
hash = ((hash << 5) + hash) + (*str);
}
return hash;
}
static size_t
hash_test(void *key)
{
unsigned int i = djb_hash(key, strlen(key));
return i;
}
static int
cmp_test(struct hash_node *node, void *key)
{
struct method_t *t = container_of(node, struct method_t, node);
int i = hash_test(key);
return t->hash == i;
}
static struct hashmap map;
void JNICALL
callbackMethodEntry(jvmtiEnv *jvmti, JNIEnv* env,
jthread thr, jmethodID method) {
char *name;
char *signature;
char *generic;
(*jvmti)->GetMethodName(jvmti, method, &name, &signature, &generic);
if (strcmp(name, "bar") == 0){
jvmtiThreadInfo info;
jlong nanos;
struct method_t *t;
char key[KEY_MAX_LENGTH] = "thread-";
(*jvmti)->GetThreadInfo(jvmti, thr, &info);
strcat(key, info.name);
strcat(key, ":");
strcat(key, name);
strcat(key, "\0");
(*jvmti)->GetTime(jvmti, &nanos);
t = calloc(1, sizeof(*t));
t->hash = hash_test(key);
t->start_time = nanos;
hashmap_insert(&map, &t->node, key);
(*jvmti)->Deallocate(jvmti, (void *)info.name);
}
}
void JNICALL
callbackMethodExit(jvmtiEnv *jvmti, JNIEnv* env,
jthread thr, jmethodID method){
char *name;
char *signature;
char *generic;
(*jvmti)->GetMethodName(jvmti, method, &name, &signature, &generic);
if (strcmp(name, "bar")== 0){
jvmtiThreadInfo info;
jlong nanos;
struct method_t *t;
char key[KEY_MAX_LENGTH] = "thread-";
(*jvmti)->GetThreadInfo(jvmti,
thr,
&info);
strcat(key, info.name);
strcat(key, ":");
strcat(key, name);
struct hash_node *node = hashmap_get(&map, key);
if (node == NULL) {
printf("%s not found\n", key);
} else {
(*jvmti)->GetTime(jvmti, &nanos);
t = container_of(node, struct method_t, node);
printf("method<%s> running: %ld ms\n", key, (nanos - t->start_time) / (1000 * 1000));
}
(*jvmti)->Deallocate(jvmti, (void *)info.name);
}
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){
jvmtiEnv *jvmti = NULL;
jvmtiError error;
hashmap_init(&map, hash_test, cmp_test);
//获取JVMTI environment
error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_1);
if (error != JNI_OK) {
fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
return JNI_ERR;
}
//注册功能
jvmtiCapabilities capabilities;
(void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
capabilities.can_generate_method_entry_events = 1;
capabilities.can_generate_method_exit_events = 1;
capabilities.can_access_local_variables = 1;
error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
return error;
}
//设置JVM事件回调
jvmtiEventCallbacks callbacks;
callbacks.MethodEntry = &callbackMethodEntry;
callbacks.MethodExit = &callbackMethodExit;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");
return error;
}
//设置事件通知
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
return JNI_OK;
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
//do nothing
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm){
hashmap_free(&map);
}
编译时,增加hashmap.c,运行后输出结果如下:
JVMTI提供了对JVM内部所有线程生命周期的监控事件,并可以控制这些线程的行为,比如当一个Java线程开始执行时触发JVMTI_EVENT_THREAD_START事件,结束时触发JVMTI_EVENT_THREAD_END,通过实现回调函数,可以获得触发该事件下的线程,并获取线程信息或操作该线程。
我们可以记录该线程的执行和CPU-Time,参考代码如下:
#include
#include
#include
#include
void JNICALL
callbackThreadStart(jvmtiEnv *jvmti, JNIEnv* env, jthread thr){
jvmtiThreadInfo info;
jlong cpu_time;
jvmtiFrameInfo frames[5];
jint count;
jvmtiError err;
//获取启动线程信息
(*jvmti)->GetThreadInfo(jvmti, thr, &info);
//获取启动线程CPU-Time
(*jvmti)->GetThreadCpuTime(jvmti, thr, &cpu_time);
printf("thread-%s start...\n", info.name);
printf("thread-%s cpu-time : %ld nanos\n", info.name, cpu_time);
}
void JNICALL callbackThreadEnd(jvmtiEnv *jvmti,
JNIEnv* env,
jthread thr){
jvmtiThreadInfo info;
(*jvmti)->GetThreadInfo(jvmti,
thr,
&info);
printf("thread-%s end...\n", info.name);
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){
jvmtiEnv *jvmti = NULL;
jvmtiError error;
//获取JVMTI environment
error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_1);
if (error != JNI_OK) {
fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
return JNI_ERR;
}
//注册功能
jvmtiCapabilities capabilities;
(void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
capabilities.can_get_current_thread_cpu_time = 1 ;
capabilities.can_get_thread_cpu_time = 1 ;
error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
return error;
}
//jvmtiEventThreadStart ThreadStart;
//jvmtiEventThreadEnd ThreadEnd;
//设置JVM事件回调
jvmtiEventCallbacks callbacks;
callbacks.ThreadStart = &callbackThreadStart;
callbacks.ThreadEnd = &callbackThreadEnd;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");
return error;
}
//设置事件通知
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_START, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
return JNI_OK;
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
//do nothing
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm){
//do nothing
}
运行后输出结果如下:
JVMTI提供了对Moniter的支持,可以监视Lock,并且可以获取一个执行方法的本地对象,我们可以结合Method完成一个父子线程关系的监视,参考代码如下:
#include
#include
#include
void JNICALL
callbackMethodEntry(jvmtiEnv *jvmti, JNIEnv *env, jthread thr, jmethodID method) {
jrawMonitorID monitor;
char *name;
(*jvmti)->RawMonitorEnter(jvmti, monitor);
(*jvmti)->GetMethodName(jvmti, method, &name, NULL, NULL);
if (strcmp(name, "start") == 0 || strcmp(name, "interrupt") == 0 ||
strcmp(name, "join") == 0 || strcmp(name, "stop") == 0 ||
strcmp(name, "suspend") == 0 || strcmp(name, "resume") == 0){
jobject *thd_ptr;
jint hash_code;
jvmtiThreadInfo info;
//获取子线程对象
(*jvmti)->GetLocalObject(jvmti, thr, 0, 0, thd_ptr);
//获取父线程信息
(*jvmti)->GetThreadInfo(jvmti,
thr,
&info);
//获取子线程对象hashcode
(*jvmti)->GetObjectHashCode(jvmti, *thd_ptr, &hash_code);
printf("\n" , info.name, name, hash_code);
(*jvmti)->Deallocate(jvmti, (void *)info.name);
}
(*jvmti)->RawMonitorExit(jvmti, monitor);
}
void JNICALL
callbackMethodExit(jvmtiEnv *jvmti, JNIEnv* env, jthread thr, jmethodID method){
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){
jvmtiEnv *jvmti = NULL;
jvmtiError error;
//获取JVMTI environment
error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_1);
if (error != JNI_OK) {
fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
return JNI_ERR;
}
//注册功能
jvmtiCapabilities capabilities;
(void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
capabilities.can_generate_method_entry_events = 1;
capabilities.can_generate_method_exit_events = 1;
capabilities.can_access_local_variables = 1;
capabilities.can_get_monitor_info = 1;
error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
return error;
}
//设置JVM事件回调
jvmtiEventCallbacks callbacks;
callbacks.MethodEntry = &callbackMethodEntry;
callbacks.MethodExit = &callbackMethodExit;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");
return error;
}
//设置事件通知
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
return JNI_OK;
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
//do nothing
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm){
//do nothing
}
运行后输出结果如下:
JVMTI可以获取当前JVM下所有线程以及线程内执行方法的Stack Trace。
我们需要在JVMTI_EVENT_VM_INIT事件被触发时,在回调函数中利用RunAgentThread方法创建一个Agent级别的thread(创建过程非常类似pthread),之后按固定时间间隔,获取相关线程的Stack Trace信息。
参考如下代码:
#include
#include
#include
#include
#include
#include
static jthread mt;
static JNICALL
monitor_runnable(jvmtiEnv* jvmti, JNIEnv* env, jthread thr, int *args){
jthread* threads;
jint thread_count;
while (1) {
printf("----------------------Stack Trace-------------------------\n");
int i;
//获取当前JVM所有线程
(*jvmti)->GetAllThreads(jvmti, &thread_count, &threads);
for(i = 0; i < thread_count; i++){
jvmtiFrameInfo frames[5];
jint count;
jvmtiError err;
err = (*jvmti)->GetStackTrace(jvmti, threads[i], 0, 5, frames, &count);
if (err == JVMTI_ERROR_NONE && count >= 1) {
char *methodName;
err = (*jvmti)->GetMethodName(jvmti, frames[0].method,
&methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("Thread Stack Trace Executing method: %s\n", methodName);
}
}
}
sleep(2);
}
}
void JNICALL
callbackVMInit(jvmtiEnv *jvmti,
JNIEnv* env,
jthread thr){
(*jvmti)->RunAgentThread(jvmti,
thr,
(void *)monitor_runnable,
(void *)NULL,
1);
}
void JNICALL
callbackVMDeath(jvmtiEnv *jvmti,
JNIEnv* env){
(*jvmti)->StopThread(jvmti,
mt,
NULL);
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){
jvmtiEnv *jvmti = NULL;
jvmtiError error;
//获取JVMTI environment
error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_1);
if (error != JNI_OK) {
fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
return JNI_ERR;
}
//注册功能
jvmtiCapabilities capabilities;
(void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
capabilities.can_get_current_thread_cpu_time = 1 ;
capabilities.can_get_thread_cpu_time = 1 ;
capabilities.can_signal_thread = 1;
capabilities.can_pop_frame = 1;
error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
return error;
}
//jvmtiEventThreadStart ThreadStart;
//jvmtiEventThreadEnd ThreadEnd;
//设置JVM事件回调
jvmtiEventCallbacks callbacks;
callbacks.VMInit = &callbackVMInit;
callbacks.VMDeath = &callbackVMDeath;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");
return error;
}
//设置事件通知
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, (jthread)NULL);
if(error != JVMTI_ERROR_NONE) {
fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!");
return error;
}
return JNI_OK;
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
//do nothing
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm){
//do nothing
}
运行后输出结果如下: