4 basic operations and 2 interesting conclusions of JVMTI for Android performance monitoring

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

f936ccc76a648280facad34a06380eb3.png

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

  1. Real-time collection of object allocations (including the number and size of object allocations)
  2. Record GC events to help analyze memory leaks
  3. log thread activity
  4. 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:

  1. Set callback method
  2. 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.

%}$08KF1ZE)9D8S84S{)9TB.png

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

image.png

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

Guess you like

Origin blog.csdn.net/wang382758656/article/details/124577208