Java应用程序运行时监控方法(一)——JVMTI的应用
来源:互联网 发布:月中天 知乎 编辑:程序博客网 时间:2024/06/05 17:53
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包,运行后输出结果如下:
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 JNICALLAgent_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 JNICALLAgent_OnAttach(JavaVM* vm, char *options, void *reserved){ //do nothing}JNIEXPORT void JNICALLAgent_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 intdjb_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_thash_test(void *key){ unsigned int i = djb_hash(key, strlen(key)); return i;}static intcmp_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 JNICALLcallbackMethodEntry(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 JNICALLcallbackMethodExit(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 JNICALLAgent_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 JNICALLAgent_OnAttach(JavaVM* vm, char *options, void *reserved){ //do nothing}JNIEXPORT void JNICALLAgent_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 JNICALLcallbackThreadStart(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 JNICALLAgent_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 JNICALLAgent_OnAttach(JavaVM* vm, char *options, void *reserved){ //do nothing}JNIEXPORT void JNICALLAgent_OnUnload(JavaVM *vm){ //do nothing}
运行后输出结果如下:
JVMTI提供了对Moniter的支持,可以监视Lock,并且可以获取一个执行方法的本地对象,我们可以结合Method完成一个父子线程关系的监视,参考代码如下:
#include <stdio.h>#include <memory.h>#include <jvmti.h>void JNICALLcallbackMethodEntry(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 thread@%d>\n", info.name, name, hash_code); (*jvmti)->Deallocate(jvmti, (void *)info.name); } (*jvmti)->RawMonitorExit(jvmti, monitor);}void JNICALLcallbackMethodExit(jvmtiEnv *jvmti, JNIEnv* env, jthread thr, jmethodID method){}JNIEXPORT jint JNICALLAgent_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 JNICALLAgent_OnAttach(JavaVM* vm, char *options, void *reserved){ //do nothing}JNIEXPORT void JNICALLAgent_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 JNICALLmonitor_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 JNICALLcallbackVMInit(jvmtiEnv *jvmti, JNIEnv* env, jthread thr){ (*jvmti)->RunAgentThread(jvmti, thr, (void *)monitor_runnable, (void *)NULL, 1);}void JNICALLcallbackVMDeath(jvmtiEnv *jvmti, JNIEnv* env){ (*jvmti)->StopThread(jvmti, mt, NULL); }JNIEXPORT jint JNICALLAgent_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 JNICALLAgent_OnAttach(JavaVM* vm, char *options, void *reserved){ //do nothing}JNIEXPORT void JNICALLAgent_OnUnload(JavaVM *vm){ //do nothing}
运行后输出结果如下:
- Java应用程序运行时监控方法(一)——JVMTI的应用
- 基于 JVMTI 实现 Java 线程的监控
- 基于 JVMTI 实现 Java 线程的监控
- 基于 JVMTI 实现 Java 线程的监控
- Android使用monitor监控应用运行时状态(一)
- Java 运行时监控第 3 部分: 监控应用程序生态系统的性能与可用性
- Java 运行时监控,第 3 部分: 监控应用程序生态系统的性能与可用性
- Java 运行时监控,第 3 部分: 监控应用程序生态系统的性能与可用性
- Java 运行时监控,第 3 部分: 监控应用程序生态系统的性能与可用性
- Docker中监控Java应用程序的方法!
- Object - C运行时应用(一)—— 拦截系统自带的方法交换实现
- 在Docker中监控Java应用程序的5个方法
- 通过jvisualvm监控远程java应用的运行情况
- 基于jvmti和c++实现的class加密解密(一)
- java如何监控一个方法的运行时间 [问题点数
- 将java应用程序打包成独立运行的.exe方法
- 将java应用程序打包成独立运行的.exe方法
- java学习——使用Eclipse进行单元测试,报错Can't allocate jvmti memory
- Jquery 获取 radio选中值
- ORACLE 用sys.dbms_sql执行SQL例
- Hibernate缓存机制
- Serializable和Parcelable的区别
- 大富豪5.3全网首发,真正的5.3正版破解授权,不是高防端
- Java应用程序运行时监控方法(一)——JVMTI的应用
- java nio
- Python小练习
- 6月份英语总结
- oracle导入导出命令
- PHP7新特性的介绍
- H5项目常见问题汇总及解决方案
- crontab定时任务打包备份文件并删除过期文件
- NLog日志工具的使用及配置-输出年/月文件夹