1. 程式人生 > >Java應用程式執行時監控方法(一)——JVMTI的應用

Java應用程式執行時監控方法(一)——JVMTI的應用

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)

接下來,我們通過舉例的方式,看看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包,執行後輸出結果如下:

Bytecode Instrumentation

使用 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 <stdio.h>
#include <memory.h>
#include <string.h>
#include <jvmti.h>

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

執行後輸出結果如下:

Method執行效能

JVMTI提供了對每個Java方法執行的監控事件,當進入方法時觸發JVMTI_EVENT_METHOD_ENTRY事件,方法執行完成觸發JVMTI_EVENT_METHOD_EXIT,我們可以為兩個事件編寫回調函式完成對指定方法的執行效能資料的記錄。

我們使用一個HashMap資料結構來對方法的執行過程進行儲存,key為執行方法的執行緒標識+方法名,value記錄Entry方法時的系統nanos。(本例中hashmap採用https://github.com/japeq/hashmap

實現一個記錄bar方法執行時的執行時間的邏輯,參考如下程式碼實現:

#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
#include <memory.h>
#include <string.h>
#include <stdlib.h>
#include <jvmti.h>
#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,執行後輸出結果如下:

Thread監視

JVMTI提供了對JVM內部所有執行緒生命週期的監控事件,並可以控制這些執行緒的行為,比如當一個Java執行緒開始執行時觸發JVMTI_EVENT_THREAD_START事件,結束時觸發JVMTI_EVENT_THREAD_END,通過實現回撥函式,可以獲得觸發該事件下的執行緒,並獲取執行緒資訊或操作該執行緒。

我們可以記錄該執行緒的執行和CPU-Time,參考程式碼如下:

#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <jvmti.h>

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 <stdio.h>
#include <memory.h>
#include <jvmti.h>

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("<thread-%s %s [email protected]%d>\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
}

執行後輸出結果如下:

獲取Stack Trace

JVMTI可以獲取當前JVM下所有執行緒以及執行緒內執行方法的Stack Trace。

我們需要在JVMTI_EVENT_VM_INIT事件被觸發時,在回撥函式中利用RunAgentThread方法建立一個Agent級別的thread(建立過程非常類似pthread),之後按固定時間間隔,獲取相關執行緒的Stack Trace資訊。

參考如下程式碼:

#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <jvmti.h>


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
}

執行後輸出結果如下: