内存之-LeakCanary

news/2024/7/10 4:40:32 标签: 性能, 优化, leakcanary, 面试

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

目录

  • 一、导读
  • 二、概览
  • 三、使用
  • 四、原理分析
    • 4.1 自动初始化
      • 4.1.1 初始化
    • 4.2 LeakCananry自动检测步骤
      • 4.2.1 检测泄漏
      • 4.2.2 dump
      • 4.2.3 shark分析
      • 4.2.4 生成报告
  • 五、 推荐阅读

在这里插入图片描述

一、导读

我们继续总结学习基础知识,温故知新。
本文主讲 LeakCanary 使用及原理。

截止本文写稿,目前LeakCanary已经更新到2.12的版本, 我们基于2.x的版本来查看源码。

二、概览

LeakCanary主要有两大作用,第一发现内存泄漏问题,第二根据内存的状态输出泄漏的堆栈。

LeakCanary 的核心原理是主要通过 Android 生命周期的 api 来监听 activities 和 fragments 什么时候被销毁,
被销毁的对象会被传递给一个 ObjectWatcher,它持有它们的弱引用,默认等待5秒后观察弱引用是否进入关联的引用队列,
是则说明未发生泄露,否则说明可能发生泄漏。

LeakCanary 是我们熟悉内存泄漏检测工具,它能够帮助开发者非常高效便捷地检测 Android 中常见的内存泄漏。
在各大厂自研的内存泄漏检测框架(如腾讯 Matrix 和快手 Koom)的帮助文档中,也会引述 LeakCanary 原理分析。

LeakCanary Github
LeakCanary 官网

在Java中有四大引用,具体可查看下面这篇文章
强引用:绝不回收
软引用:内存不足才回收
弱引用:GC 就回收
虚引用:等价于没有引用,只是用来标识下指向的对象是否被回收。

三、使用

To use LeakCanary, add the leakcanary-android dependency to your app’s build.gradle file:

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

这样就可以了,LeakCanary会自动完成初始化,自动检测以下对象的泄漏:
Activity 、Fragment 、fragment View 、ViewModel 、Service等

我们也可以监听自己想要监听的任意对象,使用方式如下:

AppWatcher.objectWatcher.watch(object, "what you want to watcher")

四、原理分析

4.1 自动初始化

利用ContentProvider原理,ContentProvider的onCreate是在 Application的onCreate之前执行
因此在App进程拉起时会自动执行 AppWatcherInstaller 的onCreate生命周期,利用Android这种机制就可以完成自动初始化。

我们也可以关闭自动注册,进行手动注册。只需要在资源文件里覆写 @bool/eak_canary_watcher_auto_install 布尔值来关闭自动初始化,

<application>
    <provider
        android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
        android:authorities="${applicationId}.leakcanary-installer"
        android:enabled="@bool/leak_canary_watcher_auto_install"
        android:exported="false"/>
</application>


<resources>
    <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>

手动调用 AppWatcher.manualInstall 。

4.1.1 初始化

在初始化时,需要做以下几个操作

  1. 注册前后台切换监听、前台 Activity 监听和 ObjectWatcher 的泄漏监听
  2. 注册Activity的生命周期监听、fragment的生命周期监听、service等监听

我们来看看源码,都有哪些监听

  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),                    // activity
      FragmentAndViewModelWatcher(application, reachabilityWatcher),        // fragment
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }

在这个方法中,添加了我们常用的4中监听:

  • Fragment的生命周期期监听:同样,注册** FragmentManager.FragmentLifecycleCallbacks** ,但Fragment较为复杂,因为Fragment有三种,
    即android.app.Fragment、androidx.fragment.app.Fragment、android.support.v4.app.Fragment,因此需要注册各自包下的FragmentManager.FragmentLifecycleCallbacks;
  • view
  • service
  • Activity的生命周期监听:注册 Application.ActivityLifecycleCallbacks

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

4.2 LeakCananry自动检测步骤

  1. 检测可能泄漏的对象;

  2. 堆快照,生成hprof文件;

  3. 分析hprof文件;

  4. 对泄漏进行分类。

4.2.1 检测泄漏

当在初始化时各种监听注册好之后,就到对象的监听者ObjectWatcher上场了。

利用引用对象可感知对象垃圾回收的机制判定内存泄漏: 为无用对象包装弱引用,并在一段时间后(默认为五秒)观察弱引用是否如期进
入关联的引用队列,是则说明未发生泄漏,否则说明发生泄漏(无用对象被强引用持有,导致无法回收,即泄漏)

@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) " ($description)" else "") +
          " with key $key"
    }
 
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

这里其实就用到了弱引用的第二个构造方法,我们注意上面源码中的KeyedWeakReference及queue,如果弱引用关联的的对象被回收,则会把这个弱引用加入到queue中,利用这个机制可以在后续判断对象是否被回收。
我们一起来看看WeakReference构造方法

public WeakReference(T referent) {
    super(referent);
}

/**
 * 当GC回收对象时,将引用对象回收而将被引用对象放入ReferenceQueue
 */
public WeakReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
}

对于检测的入口方法在private fun moveToRetained(key: String),当检测到泄漏后就进行dump,其步骤为:
1、移除不可达对象:移除** ReferenceQueue** 中记录的KeyedWeakReference 对象(引用着监听的对象实例);
2、主动触发GC:回收不可达的对象;
3、再次移除不可达对象:经过一次GC后可以进一步导致只有WeakReference持有的对象被回收;
4、判断是否还有剩余的监听对象存活,且存活的个数是否超过阈值;
5、若满足上面的条件,则抓取Hprof文件

调用的接口为onObjectRetained,可自行跟踪代码查看

fun interface OnObjectRetainedListener {

  /**
   * A watched object became retained.
   */
  fun onObjectRetained()
  
  ...
}

4.2.2 dump

Debug.dumpHprofData(path);

    // 最终调用 Debug.dumpHprofData(heapDumpFile.absolutePath) 
    configProvider().heapDumper.dumpHeap(heapDumpFile)

关于hprof文件的内容会比较多,可自行学习

4.2.3 shark分析

Leakcanary2.x版本开源了自己实现的hprof文件解析以及泄漏引用链查找的功能模块(命名为shark),之前使用的是HAHA库,但是存在一些问题。

private fun analyzeHeap(
    heapDumpFile: File,
    progressListener: OnAnalysisProgressListener,
    isCanceled: () -> Boolean
): HeapAnalysis {
    ...
    // Shark 堆快照分析器
    val heapAnalyzer = HeapAnalyzer(progressListener)
    ...
    // 构建对象图信息
    val sourceProvider = ConstantMemoryMetricsDualSourceProvider(ThrowingCancelableFileSourceProvider(heapDumpFile)
    val graph = sourceProvider.openHeapGraph(proguardMapping = proguardMappingReader?.readProguardMapping())
    ...
    // 开始分析
    heapAnalyzer.analyze(
    heapDumpFile = heapDumpFile,
    graph = graph,
    leakingObjectFinder = config.leakingObjectFinder, // 默认是 KeyedWeakReferenceFinder
    referenceMatchers = config.referenceMatchers, // 默认是 AndroidReferenceMatchers
    computeRetainedHeapSize = config.computeRetainedHeapSize, // 默认是 true
    objectInspectors = config.objectInspectors, // 默认是 AndroidObjectInspectors
    metadataExtractor = config.metadataExtractor // 默认是 AndroidMetadataExtractor
    )
}

4.2.4 生成报告

进过一系列的分析后,就会生成一份报告。

五、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

ddd


http://www.niftyadmin.cn/n/5285979.html

相关文章

nosql--RedisTemplate定制化

nosql--RedisTemplate定制化 1、序列化2、如果使用redis中保存数据会使用默认的序列化机制&#xff0c;导致redis中保存的对象不可视2.1将所有的对象以JSON的形式保存2.2配置reids自定义配置2.3转化成功2.4配置文件代码 3redis客户端 1、序列化 stringRedisTemplate RedisTemp…

市场复盘总结 20231225

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整 昨日回顾&#xff1a; SELECT CODE,成交额排名,净流入排名,代码,名称,DDE大单金额,涨幅,主力净额,DDE大单净量,CONVERT(DATETIME, 最后封…

哔哩哔哩HarmonyOS服务卡片开发

#HarmonyOS征文#完整服务卡片项目开发&#xff0c;为Bilibili添加服务卡片。 项目预览视频播放地址 介绍 这是一款纯鸿蒙版的哔哩哔哩服务卡片应用。 6月2日鸿蒙发布&#xff0c;今年的六月已经被鸿蒙刷屏了。从安卓到鸿蒙&#xff0c;最直观的变化应该就是服务卡片了。我也…

力扣经典面试题——搜索二维矩阵(两次二分搜索)

https://leetcode.cn/problems/search-a-2d-matrix/description/?envTypestudy-plan-v2&envIdtop-100-liked 思路&#xff1a;先按行二分&#xff0c;再按列进行二分。即先找到对应的行&#xff0c;再找对应的列。 对于这种判断是否存在某个数&#xff0c;记得while(left…

目标追踪:使用ByteTrack进行目标检测和跟踪

BYTE算法是一种简单而有效的关联方法&#xff0c;通过关联几乎每个检测框而不仅仅是高分的检测框来跟踪对象。这篇博客的目标是介绍ByteTrack以及多目标跟踪&#xff08;MOT&#xff09;的技术。我们还将介绍在样本视频上使用ByteTrack跟踪运行YOLOv8目标检测。 多目标跟踪&…

【Vue2+3入门到实战】(4)Vue基础之指令修饰符 、v-bind对样式增强的操作、v-model应用于其他表单元素 详细示例

目录 一、今日学习目标1.指令补充 二、指令修饰符1.什么是指令修饰符&#xff1f;2.按键修饰符3.v-model修饰符4.事件修饰符 三、v-bind对样式控制的增强-操作class1.语法&#xff1a;2.对象语法3.数组语法4.代码练习 四、京东秒杀-tab栏切换导航高亮1.需求&#xff1a;2.准备代…

IAP在编程升级

以STM32F103ZET6为例讲解&#xff0c; FLASH 512KB,SRAM64KB. 让APP程序加载在FLASH里运行&#xff0c;在SRAM运行的先不讲解。 IAP执行流程 当加入 IAP 程序之后&#xff0c;程序运行流程如图。 APP程序的生成步骤 1.APP 程序起始地址设置方法 我们设置起始地址&#xff…

博客摘录「 Apollo安装和基本使用」2023年11月27日

常见配置中心对比 Spring Cloud Config: https://github.com/spring-cloud/spring-cloud-configApollo: https://github.com/ctripcorp/apolloNacos: https://github.com/alibaba/nacos 对比项目/配置中心 spring cloud config apollo nacos(重点) 开源时间 2014.9 2016…