内存之-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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/270102.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA分频电路设计(2)

实验要求&#xff1a; 采用 4 个开关以二进制形式设定分频系数&#xff08;0-10&#xff09;&#xff0c;实现对已知信号的分频。 类似实验我之前做过一次&#xff0c;但那次的方法实在是太笨了&#xff1a; 利用VHDL实现一定系数范围内的信号分频电路 需要重做以便将来应对更…

redis—String字符串

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

vue3使用mixins

<template><div>{{ num }}___{{ fav }}</div><button click"favBtn">改变值</button> </template><script setup lang"ts"> import mixin from "../mixins/mixin"; let { num, fav, favBtn } mixin(…

椭球面系列---射线与椭球面的交点

射线与椭球体的交点问题的求解是一个非常常见和经典的问题&#xff0c;本文给出具体的计算原理和矩阵表达的过程&#xff0c;便于编程计算。 见下图&#xff0c;已知射线(点为 p 0 \textbf{p}_0 p0​&#xff0c;单位方向为 d \textbf{d} d)&#xff0c;那么与椭球面的交点 p …

2023-12-22 回溯算法

回溯思想 回溯模版三部曲&#xff1a; ① 回溯函数模版返回值以及参数 ② 回溯终止条件 ③ 回溯搜索的遍历过程 分析完过程&#xff0c;回溯算法模板框架如下&#xff1a; void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&…

Autosar CAN开发02(入门Autosar)

Autosar架构 想起当时刚毕业进入公司之后&#xff0c;我的岗位是Autosar Bsw软件工程师。 看着这个什么“Autosar”&#xff0c;真的是一脸懵。 后来才知道&#xff0c;按照我的理解&#xff1a;Autosar就是一个软件架构。它分为ASW和BSW。ASW负责实现应用层功能&#xff08…

说个真事,裁员真的会降本增笑

最近互联网公司放烟花的次数有些高&#xff0c;基本都扎堆 Q3~Q4 出现各类事件/事故。吃瓜都快跟不上了。 作为互联网民工&#xff0c;为什么裁员后会导致降本增笑呢&#xff1f;今天我们一起来聊聊。 各种事故烟花 现阶段各大厂都领上号了&#xff0c;阿里先崩&#xff0c;…

CEC2013(python):六种算法(RFO、PSO、CSO、WOA、DBO、ABC)求解CEC2013

一、六种算法简介 1、红狐优化算法RFO 2、粒子群优化算法PSO 3、鸡群优化算法CSO 4、鲸鱼优化算法WOA 5、蜣螂优化算法DBO 6、人工蜂群算法 &#xff08;Artificial Bee Colony Algorithm, ABC&#xff09; 二、6种算法求解CEC2013 &#xff08;1&#xff09;CEC2013简…

Java中的内部类、枚举

内部类、枚举 内部类成员内部类静态内部类局部内部类&#xff08;不重要&#xff09;匿名内部类&#xff08;重要&#xff09;什么是匿名内部类使用场景 枚举类什么是枚举类枚举类的特点枚举类提供的一些额外API拓展&#xff1a;抽象枚举使用枚举类实现单例设计模式 常见应用场…

部署谷歌的Gemini大模型

前言 本文将介绍如何使用Docker、Docker-Compose私有化部署谷歌的Gemini大模型&#xff0c;以及没有服务器的情况下如何使用Vercel来部署。 Demo: 使用新加坡云服务器部署&#xff1a;Gemini Pro Chat (snowice.eu.org) 使用Vercel部署&#xff1a;Gemini Pro Chat (snowice.eu…

【美团大数据面试】Java面试题附答案

目录 1.多线程代码示例 2.单例代码示例 3.LinkedBlockingQueue原理解析 4.模板设计模式讲解 5.生产者-消费者队列设计方法 6.堆内存和栈内存的区别 7.ThreadLocal底层机制 8.synchronized原理&#xff0c;存在的问题&#xff0c;解决方案 9.volatile使用场景和原理&am…

一篇讲透:箭头函数、普通函数有什么区别

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 什么是箭头函数 箭头函数和普通函数的区别 更简洁的语法 箭头函数…

【WPF.NET开发】数据绑定应用场景

目录 1、实现属性更改通知 示例 2、双向绑定​​​更新源 示例 3、对分层数据使用主-从模式 示例 4、对分层 XML 数据使用主-从模式 示例 5、绑定两个控件的属性 示例 6、创建和绑定到 ObservableCollection 示例 7、使用 XMLDataProvider 和 XPath 查询绑定到 XML…

Java@RequestParam注解和@RequestBody注解接收参数

目录 Java后端接收数据 第一章、后端不写任何注解情况下接收参数1.1&#xff09;后端不写注解postman发出get请求1.2&#xff09;后端不写注解postman发出post请求 第二章、后端写RequestParam注解接收参数2.1&#xff09;postman发出post请求2.2&#xff09;postman发出get请求…

锂电池搅拌机的设备健康管理解决方案

随着电动车辆和可再生能源市场的迅速发展&#xff0c;锂电池作为一种重要的能源存储产品&#xff0c;正变得越来越重要。而锂电池搅拌机作为锂电池生产线中的核心设备之一&#xff0c;其正常运行对于生产线的高效稳定至关重要。为了确保锂电池搅拌机的可靠性和设备寿命&#xf…

SQL进阶理论篇(二十一):基于SQLMap的自动化SQL注入

文章目录 简介获取当前数据库和用户信息获取MySQL中的所有数据库名称查询wucai数据库中的所有数据表查看heros数据表中的所有字段查询heros表中的英雄信息总结参考文献 简介 从上一小节&#xff0c;可以发现&#xff0c;如果我们编写的代码存在着SQL注入的漏洞&#xff0c;后果…

android内存管理机制概览

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、相关概念3.1 垃圾回收3.2 应用内存的分配与回…

crtc 原理

CRTC Streams the framebuffer following the screen’s timings Driving screens : the CRT ControllerDriving screens : the CRT Controller Streams the framebuffer following the screen’s timings After each line, the CRTC must wait for the CRT to go back to th…

GoDance分布式搜索引擎项目

目录 前言一、布尔模型二、 实用评分函数1. 查询归一化因子2. 协调因子3. TF-IDF3.1 TF3.2 IDF3.3 字段长度归一值BOOST 4. 向量空间模型具体方案 三、按受欢迎度提升权重四、实时搜索与相关搜索五、具体实现方案1. 布尔模型2. 评分函数3. 实时相关搜索 前言 5月6日参加了字节…

<script setup> 的作用

一、使用<script setup> 之后&#xff0c;就不需要手动写以下代码&#xff0c;只要写逻辑代码 未加setup&#xff0c;vite 工程要加上下面代码 *export default{ * setup(){ * //只要写逻辑代码 * return{***} * } * } 加了setup &#xff0c;export default 、…