IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> JVMTI 用于Android 性能监控的4种基操和2个有趣的小结论 -> 正文阅读

[移动开发]JVMTI 用于Android 性能监控的4种基操和2个有趣的小结论

一、什么是JVMTI

JVMTI是用来开发和监控JVM所使用的程序接口,可以探查JVM内部状态,并控制JVM应用程序的执行。可实现的功能包括但不限于:

  • 调试,断点
  • 监控内存分配,回收
  • 分析线程创建结束
  • 覆盖率分析
  • 堆栈管理
  • 字节码hook等

需要注意的是,并非所有的JVM实现都支持JVMTI,Android 是 8 以后才加入的JVMTI实现。

中文文档

https://blog.caoxudong.info/blog/2017/12/07/jvmti_reference#1.1

英文文档

https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#SpecificationIntro

原理图大概是这个样子,可以看成一个中间代理人

f936ccc76a648280facad34a06380eb3.png

二、为什么接触到JVMTI

因为在最近在研究Android性能监控方面的问题,无意之间接触到这个黑科技。

三、具体能干什么

JVMTI 就是JVM给开发者的后门,你可以用它实时检测JVM的运行情况,包括对象分配,垃圾回收,线程调度,实时调试等。

四、想用它做什么

  1. 实时收集对象分配情况(包括对象分配的数量和大小)
  2. 记录GC事件,帮助分析内存泄漏
  3. 记录线程活动
  4. 记录方法调用(在方法调用和退出的时候记录运行时间)

五、如何实现上述这些功能目标

JVM 是C写的,所以你想要监听的话,需要用C/C++写一个动态连接库,然后在运行的时候attch这个库,
这个库必须要有一个主回调函数作为JVMTI的入口,如下

extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options,void *reserved)

在这个入口函数中,定制你需要监听的事件,总共有32种事件回调,你可以按需定制,回调事件越多对jvm性能影响越大,比如内存分配事件随时都在发生,而且评率非常高可能1秒钟达到1000次,又比如方法调用频率每秒钟可能达到数10000次,如果使用不当的话,程序员自己写的代码可能只写了几个方法,但是framework回调通知达到了几万次,甚至数十万次,这个就失去了监控的意义。

以下是Demo中我的Agent_OnAttach 方法如下。


/**
 * 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;

}

完整源码我会放到最后

需要注意的是要得到回调通知,首先要保证以下两点:

  1. 设置回调方法
  2. 启用对应的监听目标

下面看看上述的四种需求如何实现

用途1.记录内存分配

如果你绑定了内存分配回调,绝大多数的内存创建都会通知到回调方法,其中大部分都是系统对象的创建通知,如果你想在这里统计属于自己的new出来的内存占用也不是不可以,但频繁的通知会耗费更多的性能,实在得不偿失,但好处就是在这里可以拦截几乎所有Java层的内存分配(attch之前分配的内存拿不到)

  开启 JVMTI_EVENT_GARBAGE_COLLECTION_START 的监听  

 设置回调函数
  callbacks.VMObjectAlloc = &onObjectAllocCallback;//绑定内存分配

这样所有的内存分配都会通知到onObjectAllocCallback函数

用途2.记录GC事件

JVMTI_EVENT_GARBAGE_COLLECTION_START 和JVMTI_EVENT_GARBAGE_COLLECTION_FINISH 则是GC开始和GC结束的回调事件,结合这2个事件对内存泄漏还是有帮助,而且这2个事件产生的频率并不高,我觉得还是有比较实在的用途


    开启JVMTI_EVENT_GARBAGE_COLLECTION_START和JVMTI_EVENT_GARBAGE_COLLECTION_FINISH监听

    设置回调函数
    
    callbacks.GarbageCollectionStart = &onGCStartCallback;//GC 开始
    callbacks.GarbageCollectionFinish = &onGCFinishCallback; //GC 结束

当注册好这2个事件,就可以在onGCStartCallback 和 onGCFinishCallback 这2个方法中收到GC回调了

用途3.记录方法调用

在为 jvmtiEventCallbacks 设置好回调方法且启用对应的监听之后,当事件发生后就会收到jvm的回调事件。
比如我设置了MethodEntry和MethodExit的监听,那么当程序启动后,就会收到洪水般的回调消息

    开启JVMTI_EVENT_METHOD_ENTRY 和 JVMTI_EVENT_METHOD_EXIT监听
    
    设置回调函数
    
    callbacks.MethodEntry=&onMethodEntry;
    callbacks.MethodExit=&onMethodExit;

你可以在onMethodEntry方法中获取方法执行的的开始事件,在onMethodExit中获取方法执行的结束事件,从而计算出方法的执行时间,但是这种方法实在鸡肋了,因为jvmti是站在应用层和jvm中间的,所有jvm活动都会通知到回调方法中,其中绝大多数都是framework层的方法,这样太过于低效了,实在不适合用来监听方法调用(编译时插桩按需定制监听的方法更简单高效)。

用途4.记录线程的开始和结束

由于线程的创建相对没有那么密集,用这种方式统计线程的使用情况相对合理


    开启JVMTI_EVENT_THREAD_START和JVMTI_EVENT_THREAD_END 监听
    
    设置回调函数
    
    callbacks.ThreadStart=&onThreadStart;
    callbacks.ThreadEnd=&onThreadEnd;

你可以在onThreadStart和onThreadEnd 这2个自定义的回调方法中坚挺到线程的创建和结束

六、2个有趣的小结论是什么

1.从App启动到开启第一个只有2个按钮页的Activity执行的方法居然达到11万之多

在这个Demo执行的程序员自定义的方法不会超过10个,所以这个比例还是挺离谱的,用这种方式来计算方法执行时间意义不大。

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

2.从App启动到一个简单的Activity 页面开启,需要分配的对象个数大概是7500个左右

这个简单的Demo中程序员自己new的对象不超过10个

image.png

当然上述统计非常粗糙,不是标准知识,而是卷人的数据…

七、我遇到的问题(也或许是我的问题)

  • 1.1 在回调方法中无法加载程序员自定义的class

无法加载程序员自定的class,自然无法调用自定义Java层的方法,我想在回调方法通知到Java层,但很遗憾无法加载自定义的类,只能加载framework的和javaselib的类(说是jvmti有限制),所以要想更好的统计数据,需要写更多的C代码了

  • 1.2 我在方法MethodEntry执行的时候想打印方法所在的类名,但是必须调用Class 的getName方法,但是这个方法是Java层的,所以就造成死递归,我想通过方法名排除掉,但是很遗憾,似乎看到执行效果,调试也无法单步进入

  • 1.3 Debug 经常不工作(感觉有点鸡肋)

  • 1.4 无法保存class 和 javavm的全局变量(这个可能是我的问题)

  • 1.5 只能用于Debug阶段(但经过大佬的hack貌似也可用于release阶段,说实话,release阶段用这个东西,并不明智)

八.完整源码

https://github.com/woshiwzy/MyAndroidJVMTIDEmo

JVMTI 功能强大,我利用的只是冰山一角,以下是我参考过的大佬文章

九、参考文献

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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-06 11:10:03  更:2022-05-06 11:10:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 22:21:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码