1. What is JVMTI
JVMTI is a program interface used to develop and monitor the JVM. It can probe the internal state of the JVM and control the execution of the JVM application. Available functions include but are not limited to:
- debug, breakpoint
- Monitor memory allocation, recycling
- Analysis thread creation ends
- coverage analysis
- stack management
- Bytecode hooks, etc.
It should be noted that not all JVM implementations support JVMTI, and Android is a JVMTI implementation that was added after 8.
Chinese document
https://blog.caoxudong.info/blog/2017/12/07/jvmti_reference#1.1
English document
https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#SpecificationIntro
The schematic diagram looks like this, which can be regarded as an intermediate agent
2. Why came into contact with JVMTI
Because of the recent research on Android performance monitoring issues, I accidentally came into contact with this black technology.
Third, what can be done
JVMTI is the back door of JVM to developers. You can use it to detect the running status of JVM in real time, including object allocation, garbage collection, thread scheduling, real-time debugging, etc.
4. What do I want to use it for
- Real-time collection of object allocations (including the number and size of object allocations)
- Record GC events to help analyze memory leaks
- log thread activity
- Record method calls (record running time when method calls and exits)
5. How to achieve the above functional goals
JVM is written in C, so if you want to monitor, you need to write a dynamic link library in C/C++, and then attch this library at runtime. This library must have a main
callback function as the entry point of JVMTI, as follows
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options,void *reserved)
In this entry function, customize the events you need to monitor. There are a total of 32 event callbacks, which you can customize as needed. The more callback events, the greater the impact on jvm performance. For example, memory allocation events occur at any time, and the evaluation rate is very high. It may reach 1,000 times per second, and the frequency of method calls may reach 10,000 times per second. .
The following is my Agent_OnAttach method in Demo as follows.
/**
* Agent attch 回调
*/
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options,void *reserved) {
//VM 在这里赋值才有效,在onLoad方法里赋值,使用的时候变成了null
LOGI("JVM Agent_OnLoad: %d ,pid: %d",globalVm,getpid());
LOGI("JVM Agent_OnAttach: %d ,pid: %d",vm,getpid());
::globalVm=vm;
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
LOGI("Find helper class on onattch%s",JVM_TI_CLASS);
LOGI("Classs Exist:%d", helperClass);
// ::helperClass = env->FindClass(JVM_TI_CLASS);
//================================================
jvmtiEnv *jvmti_env = CreateJvmtiEnv(vm);
if (jvmti_env == nullptr) {
return JNI_ERR;
}
localJvmtiEnv = jvmti_env;
SetAllCapabilities(jvmti_env);
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
//设置回调函数
callbacks.VMObjectAlloc = &onObjectAllocCallback;//绑定内存分配
callbacks.NativeMethodBind = &JvmTINativeMethodBind;//
callbacks.GarbageCollectionStart = &onGCStartCallback;//GC 开始
callbacks.GarbageCollectionFinish = &onGCFinishCallback; //GC 结束
callbacks.MethodEntry=&onMethodEntry;
callbacks.MethodExit=&onMethodExit;
callbacks.ThreadStart=&onThreadStart;
callbacks.ThreadEnd=&onThreadEnd;
int error = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
//启用各种回调事件,否则可能不会触发回调方法
SetEventNotification(jvmti_env, JVMTI_ENABLE,JVMTI_EVENT_GARBAGE_COLLECTION_START);//监听GC 开始
SetEventNotification(jvmti_env, JVMTI_ENABLE,JVMTI_EVENT_GARBAGE_COLLECTION_FINISH);//监听GC 结束
SetEventNotification(jvmti_env, JVMTI_ENABLE,JVMTI_EVENT_NATIVE_METHOD_BIND);//监听native method bind
SetEventNotification(jvmti_env, JVMTI_ENABLE,JVMTI_EVENT_VM_OBJECT_ALLOC);//监听对象分配
SetEventNotification(jvmti_env, JVMTI_ENABLE,JVMTI_EVENT_OBJECT_FREE);//监听对象释放
SetEventNotification(jvmti_env, JVMTI_ENABLE,JVMTI_EVENT_CLASS_FILE_LOAD_HOOK);//监听类文件加载
SetEventNotification(jvmti_env,JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY);//方法进入
SetEventNotification(jvmti_env,JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT);//方法退出
SetEventNotification(jvmti_env,JVMTI_ENABLE, JVMTI_EVENT_THREAD_START);//线程开始
SetEventNotification(jvmti_env,JVMTI_ENABLE, JVMTI_EVENT_THREAD_END);//线程结束
LOGI("==========Agent_OnAttach=======");
return JNI_OK;
}
I will put the complete source code at the end
It should be noted that to get the callback notification, the following two points must be guaranteed first:
- Set callback method
- Enable the corresponding monitoring target
Let's take a look at how to achieve the above four requirements
Use 1. Record memory allocation
If you bind the memory allocation callback, most of the memory creation will be notified to the callback method, most of which are system object creation notifications. If you want to count the memory usage of your own new here, it is not impossible, but frequent notifications will consume more performance.
开启 JVMTI_EVENT_GARBAGE_COLLECTION_START 的监听
设置回调函数
callbacks.VMObjectAlloc = &onObjectAllocCallback;//绑定内存分配
In this way, all memory allocations will be notified to the onObjectAllocCallback function
Use 2. Record GC events
JVMTI_EVENT_GARBAGE_COLLECTION_START and JVMTI_EVENT_GARBAGE_COLLECTION_FINISH are callback events for GC start and GC end. Combining these two events is still helpful for memory leaks, and the frequency of these two events is not high. I think there are still more practical uses
开启JVMTI_EVENT_GARBAGE_COLLECTION_START和JVMTI_EVENT_GARBAGE_COLLECTION_FINISH监听
设置回调函数
callbacks.GarbageCollectionStart = &onGCStartCallback;//GC 开始
callbacks.GarbageCollectionFinish = &onGCFinishCallback; //GC 结束
When these two events are registered, you can receive GC callbacks in the two methods of onGCStartCallback and onGCFinishCallback
Use 3. Log method calls
After setting the callback method for jvmtiEventCallbacks and enabling the corresponding monitoring, when the event occurs, the jvm callback event will be received.
For example, if I set up the monitoring of MethodEntry and MethodExit, then when the program starts, it will receive a flood of callback messages
开启JVMTI_EVENT_METHOD_ENTRY 和 JVMTI_EVENT_METHOD_EXIT监听
设置回调函数
callbacks.MethodEntry=&onMethodEntry;
callbacks.MethodExit=&onMethodExit;
You can obtain the start event of method execution in the onMethodEntry method, and the end event of method execution in onMethodExit to calculate the execution time of the method, but this method is really useless, because jvmti stands between the application layer and jvm, and all jvm activities will be notified to the callback method, most of which are methods of the framework layer, which is too inefficient and not suitable for monitoring method calls.
Use 4. Record the start and end of the thread
Since the creation of threads is relatively less intensive, it is relatively reasonable to count the usage of threads in this way
开启JVMTI_EVENT_THREAD_START和JVMTI_EVENT_THREAD_END 监听
设置回调函数
callbacks.ThreadStart=&onThreadStart;
callbacks.ThreadEnd=&onThreadEnd;
You can stick to the creation and end of the thread in the two custom callback methods onThreadStart and onThreadEnd
6. What are the two interesting conclusions
1. There are as many as 110,000 methods executed from the start of the App to the opening of the first Activity with only 2 button pages
There will not be more than 10 programmer-defined methods executed in this demo, so this ratio is quite outrageous, and it is not meaningful to calculate the method execution time in this way.
2. From the start of the App to the opening of a simple Activity page, the number of objects that need to be allocated is about 7500
In this simple Demo, the programmer's own new objects do not exceed 10
Of course, the above statistics are very rough, not standard knowledge, but convoluted data...
7. The problems I encountered (or maybe my problems)
- 1.1 The programmer's custom class cannot be loaded in the callback method
Unable to load the class customized by the programmer, naturally unable to call the method of the custom Java layer. I want to notify the Java layer in the callback method, but unfortunately I cannot load the custom class. I can only load the framework and javaselib classes (it is said that jvmti has restrictions), so I need to write more C codes to get better statistics.
-
1.2 I want to print the class name of the method when the method MethodEntry is executed, but the getName method of Class must be called, but this method is in the Java layer, so it causes dead recursion. I want to exclude it through the method name, but unfortunately, it seems that I can see the execution effect, and the debugging cannot be entered in a single step.
-
1.3 Debug often does not work (feels a bit tasteless)
-
1.4 Unable to save global variables of class and javavm (this may be my problem)
-
1.5 can only be used in the Debug stage (but it seems that it can also be used in the release stage after the hack of the boss. To be honest, it is not wise to use this thing in the release stage)
Eight. Complete source code
https://github.com/woshiwzy/MyAndroidJVMTIDEmo
JVMTI is powerful, and I only use the tip of the iceberg. The following are the big articles I have referred to
9. References
https://blog.csdn.net/duqi_2009/article/details/94518203
https://blog.csdn.net/z1032689332/article/details/104477182?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.pc_relevant_default&utm_relevant_index=5
https://blog.csdn.net/zhuoxiuwu/article/details/118694396?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-6.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-6.pc_relevant_paycolumn_v3&utm_relevant_index=9