内存之-LeakCanary

news2024/9/24 9:21:59

关于作者: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.coloradmin.cn/o/1335129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 fixture 机制重构 appium_helloworld

一、前置说明 在 pytest 基础讲解 章节,介绍了 pytest 的特性和基本用法,现在我们可以使用 pytest 的一些机制,来重构 appium_helloworld 。 appium_helloworld 链接: 编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来 代码目录结构: pytest.ini 设置: [pyt…

利用FLRIT恢复符号表---笔记

文章目录 FLIRT应用FLIRT签名创建FLIRT签名文件创建签名概述识别和获取静态库创建模式文件创建签名信息 FLIRT 在某些ELF文件中&#xff0c;IDA无法识别库函数的签名。就会导致IDA加载的函数全是sub_xxxx的形式&#xff0c;这给我们的逆向工程造成极大的困难。 这个时候就可以…

Python爬虫中的多线程、线程池

进程和线程的基本介绍 进程是一个资源单位&#xff0c;线程是一个执行单位&#xff0c;CPU调度线程来执行程序代码。 当运行一个程序时&#xff0c;会给这个程序分配一个内存空间&#xff0c;存放变量等各种信息资源&#xff0c;而这个内存空间可以说是一个进程&#xff0c; 一…

Linux文件系统与命令行

什么是命令行? 接收键盘命令并将其传给操作系统执行的程序(用于输入和管理命令的程序),统称命令行,也叫: Shell&#xff0c;几乎所有Linux发行版都提供了一个 Shell 程序,叫做: Bash (Bourne-Again Shell, 因为最初的 Shell 是由 Steve Bourne 编写的原始 Unix 程序, Again 表…

ChatGPT新增聊天存档功能,可构建自己的聊天数据库啦!

12月21日&#xff0c;OpenAI在社交平台宣布&#xff0c;ChatGPT新增存档功能&#xff0c;用户可以保存聊天记录而不必删除它们。 虽然这只是一个很小的功能&#xff0c;但对于科研、医疗、写作、金融、律师等&#xff0c;对文本使用频率较高的行业帮助巨大&#xff0c;可构建自…

Vue3设计目标和优化

vue3.0的设计目标是什么&#xff1f;做了哪些优化&#xff1f; Vue3的设计目标&#xff1a;更小、更快、更友好、优化方案 一、设计目标 Vue3之前我们会面临的许多问题&#xff1a; 随着功能的增长&#xff0c;复杂组件的代码变得越来越难以维护起来缺少一种比较【干净】的…

YACS(上海计算机学会竞赛平台)一星级题集——评委打分

题目描述 许多比赛在计算选手得分平均数的时候&#xff0c;都会先去掉一个最高得分&#xff0c;再去掉一个最低得分&#xff0c;这样可以避免极大值与极小值对平均值的影响。 给定 n 个数字 a[1]​,a[2]​,⋯,a[n]​&#xff0c;表示一个选手获得的分数&#xff0c;请根据上述…

【力扣】199.二叉树的右视图

看到这个题目的一瞬间&#xff0c;我想递归&#xff0c;必须用递归。最近被递归折磨的有点狠&#xff0c;但是我感觉我快要打败它了&#xff0c;就是现在稍稍有点处于劣势。不过没关系&#xff0c;来日方长不是。 法一&#xff1a;递归 题解&#xff1a; 之前想的就是先递归&…

【Vue3+TypeScript】快速上手_笔记

前言 1. Vue3简介 2020年9月18日&#xff0c;Vue.js发布版3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;n 经历了&#xff1a;4800次提交、40个RFC、600次PR、300贡献者 官方发版地址&#xff1a;Release v3.0.0 One Piece vuejs/core 截止2023年10月&#xf…

读文献、写论文时,有什么好用的软件或网站推荐?

高考志愿、考研保研、职业规划、简历优化&#xff0c;欢迎加入《猴哥成长营》&#xff01; https://www.yuque.com/jackpop/ulig5a/srnochggbsa2eltw?singleDoc 读文献、写论文对于绝大多数本科生、研究生、博士生都是必经之路。 当突然面对这样一项任务时&#xff0c;会觉得…

NVIDIA NCCL 源码学习(十二)- double binary tree

上节我们以ring allreduce为例看到了集合通信的过程&#xff0c;但是随着训练任务中使用的gpu个数的扩展&#xff0c;ring allreduce的延迟会线性增长&#xff0c;为了解决这个问题&#xff0c;NCCL引入了tree算法&#xff0c;即double binary tree。 double binary tree 朴素…

单位圆内接三角形的角是外接三角

证明 ∠ A P C 2 ∠ A B C ∠APC2∠ABC ∠APC2∠ABC ∴ ∴ ∴ 三角形内角和为180 $∵ \begin{cases} ∠ABP∠BAP∠APB180 \∠ABC∠BAC∠ACB180 \∠PAC∠PCA∠APC180 \end{cases} $ ∴ A P B P P C r ∴APBPPCr ∴APBPPCr ∵ ∵ ∵△PAB和△PAC为等腰三角形 ∴ ∴ ∴等腰三…

redis—String字符串

目录 前言 1.字符串数据类型 2.常见命令 3.典型应用场景 前言 字符串类型是Redis最基础的数据类型&#xff0c;关于字符串需要特别注意: 1)首先Redis中所有的键的类型都是字符串类型&#xff0c;而且其他几种数据结构也都是在字符串类似基础.上构建的&#xff0c;例如列表…

ROS xacro优化URDF

Xacro是ROS中的一个工具&#xff0c;用于简化URDF文件的编写。它的主要目的是构造更短、更易读的XML文件&#xff0c;同时保持与URDF的兼容性。 以下是Xacro的基本语法和用法&#xff1a; 1、属性设置和算数运算&#xff1a; 可以使用xacro:property来定义常量或变量&#xf…

《Git快速入门》Git分支

1.master、origin、origin/master 区别 首先搞懂git分支的一些名称区别&#xff1a; master &#xff1a; Git 的默认分支名字。它并不是一个特殊分支、跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支&#xff0c;是因为 git init 命令默认创建它&#xff0c…

利用markdown语法,写出数学公式以及常用符号【持续更新!!!】

1.希腊字母 数学表达式Markdown语法α\alphaβ\betaγ\gammaδ\deltaε\epsilonζ\zetaη\etaθ\thetaι\iotaκ\kappaλ\lambdaμ\muν\nuξ\xiο\omicronπ\piρ\rhoσ\sigmaτ\tauυ\upsilonφ\phiχ\chiψ\psiω\omega 2.基本表达式 数学表达式Markdow语法xx^2y₁y_1∞\…

医学影像处理与智能医学:数据集资源和云端加速路径

医学影像处理识别是一种利用计算机技术影像进行识别、分析和处理的方法。它主要应用于医学影像学领域&#xff0c;如 X 射线、CT 扫描、MRI 和超声等。通过图像处理技术&#xff0c;可以对这些影像进行数字化处理&#xff0c;提取有用信息&#xff0c;辅助医生进行疾病诊断、治…

如何编写高效清晰的嵌入式C程序

作为嵌入式工程师&#xff0c;怎么写出效率高、思路清晰的C语言程序呢? 要用C语言的思维方式来进行程序的构架构建 要有良好的C语言算法基础&#xff0c;以此来实现程序的逻辑构架 灵活运用C语言的指针操作 虽然看起来以上的说法很抽象&#xff0c;给人如坠雾里的感觉&…

【Element】el-select下拉框实现选中图标并回显图标

一、背景 需求&#xff1a;在下拉框中选择图标&#xff0c;并同时显示图标和文字&#xff0c;以便用户可以直观地选择所需的图标。 二、功能实现 <template><div><el-table ref"table" :data"featureCustom2List" height"200"…

AXI总线协议---关键信号波形图分析

写过程协议图 读过程协议图 读协议执行顺序图 写协议顺序图 单箭头表示两个信号谁先有效无所谓&#xff0c;双箭头表示必须要等到前一个信号有效才能将后面的信号有效 如何体现协议图中的通道理解 声明&#xff1a;以上图均采用AMBA总线文档图 写过程关键信号 主机 写地址—M…