zoukankan      html  css  js  c++  java
  • OS + Android performance matrix / memory LeakCanary

    s

    matrix性能数据采集SDK集成

    https://www.jianshu.com/p/492e5b16373c

    http://ossup.s*****.com/s-c-t-s/s-c-t-s_yh_prd/Android/sdk/%E6%80%A7%E8%83%BD%E6%95%B0%E6%8D%AE%E9%87%87%E9%9B%86%E6%8E%A5%E5%85%A5%E6%96%87%E6%A1%A3.html

    一、介绍

    Matrix 是一款微信研发并日常使用的应用性能接入框架,支持iOS, macOS和Android。 Matrix 通过接入各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。

    Matrix for Android


    Matrix-android 当前监控范围包括:应用安装包大小,帧率变化,启动耗时,卡顿,慢方法,SQLite 操作优化,文件读写,内存泄漏等等。

    • APK Checker: 针对 APK 安装包的分析检测工具,根据一系列设定好的规则,检测 APK 是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪
    • Resource Canary: 基于 WeakReference 的特性和 Square Haha 库开发的 Activity 泄漏和 Bitmap 重复创建检测工具
    • Trace Canary: 监控界面流畅性、启动耗时、页面切换耗时、慢函数及卡顿等问题
    • SQLite Lint: 按官方最佳实践自动化检测 SQLite 语句的使用质量
    • IO Canary: 检测文件 IO 问题,包括:文件 IO 监控和 Closeable Leak 监控

    特性


    APK Checker
    • 具有更好的可用性:JAR 包方式提供,更方便应用到持续集成系统中,从而追踪和对比每个 APK 版本之间的变化
    • 更多的检查分析功能:除具备 APKAnalyzer 的功能外,还支持统计 APK 中包含的 R 类、检查是否有多个动态库静态链接了 STL 、搜索 APK 中包含的无用资源,以及支持自定义检查规则等
    • 输出的检查结果更加详实:支持可视化的 HTML 格式,便于分析处理的 JSON ,自定义输出等等
    Resource Canary
    • 分离了检测和分析部分,便于在不打断自动化测试的前提下持续输出分析后的检测结果
    • 对检测部分生成的 Hprof 文件进行了裁剪,移除了大部分无用数据,降低了传输 Hprof 文件的开销
    • 增加了重复 Bitmap 对象检测,方便通过减少冗余 Bitmap 数量,降低内存消耗
    Trace Canary
    • 编译期动态修改字节码, 高性能记录执行耗时与调用堆栈
    • 准确的定位到发生卡顿的函数,提供执行堆栈、执行耗时、执行次数等信息,帮助快速解决卡顿问题
    • 自动涵盖卡顿、启动耗时、页面切换、慢函数检测等多个流畅性指标
    SQLite Lint
    • 接入简单,代码无侵入
    • 数据量无关,开发、测试阶段即可发现SQLite性能隐患
    • 检测算法基于最佳实践,高标准把控SQLite质量*
    • 底层是 C++ 实现,支持多平台扩展
    IO Canary
    • 接入简单,代码无侵入
    • 性能、泄漏全面监控,对 IO 质量心中有数
    • 兼容到 Android P


    作者:momxmo
    链接:https://www.jianshu.com/p/492e5b16373c
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    LeakCanary内存泄漏采集SDK集成

    https://www.jianshu.com/p/a5e69a2e093f

    LeakCanary github地址:https://square.github.io/leakcanary/

    开始使用

    目前为止最新的版本是2.3版本,相比于2.0之前的版本,2.0之后的版本在使用上简洁了很多,只需要在dependencies中加入LeakCanary的依赖即可。而且debugImplementation只在debug模式下有效,所以不用担心用户在正式环境下也会出现LeakCanary收集。

    dependencies {
      // debugImplementation because LeakCanary should only run in debug builds.
      debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
    }
    

    在项目中加入LeakCanary之后就可以开始检测项目的内存泄露了,把项目运行起来之后, 开始随便点自己的项目,下面以一个Demo项目为例,来聊一下LeakCanary记录内存泄露的过程以及我如何解决内存泄露的。
    项目运行起来之后,在控制台可以看到LeakCanary的打印信息:

    D/LeakCanary: Check for retained object found no objects remaining
    D/LeakCanary: Scheduling check for retained objects in 5000ms because app became invisible
    D/LeakCanary: Check for retained object found no objects remaining
    D/LeakCanary: Rescheduling check for retained objects in 2000ms because found only 3 retained objects (< 5 while app visible)
    

    这说明LeakCanary正在不断的检测项目中是否有剩余对象。那么LeakCanary是如何工作的呢?LeakCanary的基础是一个叫做ObjectWatcher Android的library。它hook了Android的生命周期,当activity和fragment 被销毁并且应该被垃圾回收时候自动检测。这些被销毁的对象被传递给ObjectWatcher, ObjectWatcher持有这些被销毁对象的弱引用(weak references)。如果弱引用在等待5秒钟并运行垃圾收集器后仍未被清除,那么被观察的对象就被认为是保留的(retained,在生命周期结束后仍然保留),并存在潜在的泄漏。LeakCanary会在Logcat中输出这些日志。

    D LeakCanary: Watching instance of com.example.leakcanary.MainActivity 
    // 5 seconds later...
    D LeakCanary: Found 1 retained object
    

    在我一顿瞎点之后, 在手机通知栏上面出现了这样的提示:

     
    image.png
    上面的意思是已经发现了4个保留的对象,点击通知可以触发堆转储(dump heap)。在app可见的时候,会一直等到5个保留的对象才会触发堆转储。这里要补充的一点是:当应用可见的时候默认的阈值是5,应用不可见的时候阈值是1。如果你看到了保留的对象的通知然后将应用切换到后台(例如点击home键),那么阈值就会从5变到1,LeakCanary会立即进行堆转储。那么堆转储是什么一回事呢?
    堆转储

    在我点了上面的通知之后, 控制台打印出了下面的语句:

    D/LeakCanary: Check for retained objects found 3 objects, dumping the heap
    D/LeakCanary: WRITE_EXTERNAL_STORAGE permission not granted, ignoring
    I/testapplicatio: hprof: heap dump "/data/user/0/com.example.leakcaneraytestapplication/files/leakcanary/2020-05-28_16-35-28_155.hprof" starting...
    I/testapplicatio: hprof: heap dump completed (22MB) in 2.963s objects 374548 objects with stack traces 0
    
    这里开始进行堆转储,同时生成.hprof文件,LeakCanary将java heap的信息存到该文件中。同时在应用程序中也会出现一个提示。
     
    image.png

    LeakCanary是使用shark来转换.hprof文件并定位Java堆中保留的对象。如果找不到保留的对象,那么它们很可能在堆转储的过程中回收了。

     
    image.png
    对于每个被保留的对象,LeakCanary会找出阻止该保留对象被回收的引用链:泄漏路径。泄露路径就是从GC ROOTS到保留对象的最短的强引用路径的别名。确定泄漏路径以后,LeakCanary使用它对Android框架的了解来找出在泄漏路径上是谁泄漏了。

    解决内存泄露

    打开生成的Leaks应用,界面就类似下面这样婶儿滴。LeakCanary会计算一个泄漏路径并在UI上展示出来。这就是LeakCanary很友好的地方,通过UI展示,可以很直接的看到内存泄漏的过程。相对于mat和android studio 自带的profiler分析工具,这个简直太直观清晰了!
     
    image.png

    同时泄漏路径也在logcat中展示了出来:

    HEAP ANALYSIS RESULT
        ====================================
        1 APPLICATION LEAKS
        
        References underlined with "~~~" are likely causes.
        Learn more at https://squ.re/leaks.
        
        111729 bytes retained by leaking objects
        Signature: e030ebe81011d69c7a43074e799951b65ea73a
        ┬───
        │ GC Root: Local variable in native code
        │
        ├─ android.os.HandlerThread instance
        │    Leaking: NO (PathClassLoader↓ is not leaking)
        │    Thread name: 'LeakCanary-Heap-Dump'
        │    ↓ HandlerThread.contextClassLoader
        ├─ dalvik.system.PathClassLoader instance
        │    Leaking: NO (ToastUtil↓ is not leaking and A ClassLoader is never leaking)
        │    ↓ PathClassLoader.runtimeInternalObjects
        ├─ java.lang.Object[] array
        │    Leaking: NO (ToastUtil↓ is not leaking)
        │    ↓ Object[].[871]
        ├─ com.example.leakcaneraytestapplication.ToastUtil class
        │    Leaking: NO (a class is never leaking)
        │    ↓ static ToastUtil.mToast
        │                       ~~~~~~
        ├─ android.widget.Toast instance
        │    Leaking: YES (This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null))
        │    ↓ Toast.mContext
        ╰→ com.example.leakcaneraytestapplication.LeakActivity instance
        ​     Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
        ​     key = c1de58ad-30d8-444c-8a40-16a3813f3593
        ​     watchDurationMillis = 40541
        ​     retainedDurationMillis = 35535
        ====================================
        0 LIBRARY LEAKS
    

    路径中的每一个节点都对应着一个java对象。熟悉java内存回收机制的同学都应该知道”可达性分析算法“,LeakCanary就是用可达性分析算法,从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。

    我们从上往下看:

    GC Root: Local variable in native code
    

    在泄漏路径的顶部是GC Root。GC Root是一些总是可达的特殊对象。
    接着是:

    ├─ android.os.HandlerThread instance
        │    Leaking: NO (PathClassLoader↓ is not leaking)
        │    Thread name: 'LeakCanary-Heap-Dump'
        │    ↓ HandlerThread.contextClassLoader
    

    这里先看一下Leaking的状态(YES、NO、UNKNOWN),NO表示没泄露。那我们还得接着向下看。

     ├─ dalvik.system.PathClassLoader instance
        │    Leaking: NO (ToastUtil↓ is not leaking and A ClassLoader is never leaking)
        │    ↓ PathClassLoader.runtimeInternalObjects
    

    上面的节点告诉我们Leaking的状态还是NO,那再往下看。

       ├─ android.widget.Toast instance
        │    Leaking: YES (This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null))
        │    ↓ Toast.mContext
    

    中间Leaking是NO状态的我就不再贴出来,我们看看Leaking是YES的这一条,这里说明发生了内存泄露。
    ”This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null)“,这里说明Toast发生了泄露,android.widget.Toast 这是系统的Toast控件,这说明我们在使用Toast的过程中极有可能创建了Toast对象,但是该回收它的时候无法回收它,导致出现了内存泄露,这里我们再往下看:

    ╰→ com.example.leakcaneraytestapplication.LeakActivity instance
        ​     Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
    
    这里就很明显的指出了内存泄露是发生在了那个activity里面,我们根据上面的提示,找到对应的activity,然后发现了一段跟Toast有关的代码:
     
    image.png

    这里再进入ToastUtil这个自定义Toast类里面,看看下面的代码,有没有发现什么问题?这里定义了一个static的Toast对象类型,然后在showToast的时候创建了对象,之后就没有然后了。我们要知道static的生命周期是存在于整个应用期间的,而一般Toast对象只需要显示那么几秒钟就可以了,因为这里创建一个静态的Toast,用完之后又没有销毁掉,所以这里提示有内存泄露了。因此我们这里要么不用static修饰,要么在用完之后把Toast置为null。

    public class ToastUtil {
    
        private static Toast mToast;
    
        public static void showToast(Context context, int resId) {
            String text = context.getString(resId);
            showToast(context, text);
        }
    
        public static void showToast(Context context, String text){
            showToast(context, text, Gravity.BOTTOM);
        }
    
        public static void showToastCenter(Context context, String text){
            showToast(context, text, Gravity.CENTER);
        }
    
        public static void showToast(Context context, String text, int gravity){
            cancelToast();
            if (context != null){
                LayoutInflater inflater = (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                View layout = inflater.inflate(R.layout.toast_layout, null);
                ((TextView) layout.findViewById(R.id.tv_toast_text)).setText(text);
                mToast = new Toast(context);
                mToast.setView(layout);
                mToast.setGravity(gravity, 0, 20);
                mToast.setDuration(Toast.LENGTH_LONG);
                mToast.show();
            }
        }
    
        public static void cancelToast() {
            if (mToast != null){
                mToast.cancel();
            }
        }
    }
    

    讲了这么多,其实内存泄露的本质是长周期对象持有了短周期对象的引用,导致短周期对象该被回收的时候无法被回收,从而导致内存泄露。我们只要顺着LeakCaneray的给出的引用链一个个的往下找,找到发生内存泄露的地方,切断引用链就可以释放内存了。

    这里再补充一点上面的这个例子里面Leaking没有UNKNOWN的状态,一般情况下除了YES、NO还会出现UNKNOWN的状态,UNKNOWN表示这里可能出现了内存泄露,这些引用你需要花时间来调查一下,看看是哪里出了问题。一般推断内存泄露是从最后一个没有泄漏的节点(Leaking: NO )到第一个泄漏的节点(Leaking: YES)之间的引用。

    参考文章链接:https://www.jianshu.com/p/bcaab8f0f280



    作者:牵着蜗牛散步Zz
    链接:https://www.jianshu.com/p/a5e69a2e093f
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    end

  • 相关阅读:
    VC实现开机自启动
    用Shell扩展实现源代码统计程序
    在(CListView)列表视图中添加右键菜单的方法
    关于打开外部程序并且发送一个按键消息 (转
    vc中运行外部程序的方法
    如何在 XCode 4.2 設定部分程式碼不使用 ARC 方式分享(转)
    Xcode调试断点不停止解决方案!(转)
    NSString+NSMutableString+NSValue+NSAraay用法汇总 (转)
    对于Retain和Assign属性的理解(转)
    基于Xcode4开发第一个iPhone程序:“Hello World”(转)
  • 原文地址:https://www.cnblogs.com/lindows/p/15654486.html
Copyright © 2011-2022 走看看