LeakCanary源码分析

不要记源码细节, 记也记不住, 记涉及到的基础知识、学习思想、学习架构、学习用到的哪些设计模式

一、 监控内存泄漏

在这里插入图片描述

1.1 内存泄漏的流程
  • 1、添加Activity生命周期的监听
  • 2、Activity执行了onDestroy之后, 理论上来说, Activity应当被回收掉, 但是实际上并没有被回收掉
  • 3、此时将Activity与弱引用进行关联, 这里思考一个问题, 为什么不将该Activity与软引用进行关联?
  • 4、同时将该弱引用与引用队列进行关联, 然后将该Activity进行key唯一表示, 缓存在map中, 利用引用队列与弱引用的关系, 如果弱引用持有的对象被GC回收, 那么该对象会进入到引用队列中 (这个时候如果使用析构函数, 就可以逃脱GC回收), 对引用队列进行弹栈操作, 弹出的元素从map进行进行删除, 这个操作之后, 如果map中还缓存的有key, 则表示当前Activity没有被回收掉
  • 5、Activity此时没有被回收掉, 并不代表就发生了内存泄漏, 因为此时GC可能并没有被触发
  • 6、然后手动触发GC, 但是手动触发GC并不代表就一定发生了GC, 此时延迟100ms, 然后再次进行弹栈操作, 如果还是有key存在, 则此时认为Activity没有被回收掉
  • 7、接下来进行内存快照的采集, 这里LeakCanary采取的方式可能是为了性能, 再进行内存快照采集时, 会先判断泄漏对象的个数是否超过默认值5, 只有超过这个值, 才会进行内存快照采集, 否则只会进行通知栏展示.
1.2 LeakCanary泄漏策略与Matrix进行比较

undo留一个待解决的问题, Matrix在LeakCanary的基础上进行了改造, 弥补了LeakCanary上的一些不足

二、 HPROF文件结构

  Hprof文件中包含了 Dump时刻内存中的所有对象的信息,包括类的描述,实例的数据和引用关系,线程的栈信息
  这里说重要也重要, 说不重要也不重要, 因为都有现成的工具, 采集到的hprof文件也完全可以使用现成的工具mat进行分析.
  知其然也要知其所以然, 搞明白LeakCanary到底是如何分析hprof文件, 以后就有机会对LeakCanary分析这块儿的代码进行进一步的优化.

  • 1、GC算法
  • 2、什么样的对象可以作为GC Roots
  • 3、GC Roots的流程
2.1 HPROF文件的结构

HPROF Agent 文档
  Hprof文件使用的基本数据类型为: u1、u2、u4、u8, 分别表示1byte、2byte、4byte、8byte的内容, 由文件头(header)和文件内容(records)两部分组成.
其中文件头包含以下信息:

长度含义
[u1]*以null结尾的一串字节, 用于表示格式名称及版本, 比如JAVA PROFILE1.0.1(由18个u1字节组成)
u4size of identifiers, 即字符串、对象、堆栈等信息的id的长度(很多record的具体信息需要通过id来查找)
u8时间戳, 1970/1/1以来的毫秒数
2.2 Hprof文件定义的TAG有
enum HprofTag {  
    HPROF_TAG_STRING = 0x01,                // 字符串 
    HPROF_TAG_LOAD_CLASS = 0x02,            // 类  
    HPROF_TAG_UNLOAD_CLASS = 0x03,  
    HPROF_TAG_STACK_FRAME = 0x04,           // 栈帧  
    HPROF_TAG_STACK_TRACE = 0x05,           // 堆栈  
    HPROF_TAG_ALLOC_SITES = 0x06,  
    HPROF_TAG_HEAP_SUMMARY = 0x07,  
    HPROF_TAG_START_THREAD = 0x0A,  
    HPROF_TAG_END_THREAD = 0x0B,  
    HPROF_TAG_HEAP_DUMP = 0x0C,             // 堆  
    HPROF_TAG_HEAP_DUMP_SEGMENT = 0x1C,  
    HPROF_TAG_HEAP_DUMP_END = 0x2C,  
    HPROF_TAG_CPU_SAMPLES = 0x0D,  
    HPROF_TAG_CONTROL_SETTINGS = 0x0E
};

需要重点关注的主要是三类信息:

  • 1、字符串信息: 保存着所有的字符串, 在解析时可通过索引id引用
  • 2、类的结构信息: 包括类内部的变量布局, 在父类的信息等等
  • 3、堆信息: 内存占用与对象引用的详细信息
2.3 records部分

records由多个Record组成, 而Record又由4部分组成:

  • TAG: Record类型
  • TIME: 表示record的时间戳
  • LENGTH: BODY的字节长度
  • BODY: record中存储的数据, 例如trace、object、class、thread等信息

解析HPROF文件主要就是根据TAG, 创建对应的集合保存信息在内存中

如果是堆信息, 即TAG为HEAP_DUMP时, 那么其BODY由一系列子record组成, 这些子record同样使用TAG来区分

enum HprofHeapTag {    
  // Traditional.   
  HPROF_ROOT_UNKNOWN = 0xFF,    
  HPROF_ROOT_JNI_GLOBAL = 0x01,    // native中的全局变量  
  HPROF_ROOT_JNI_LOCAL = 0x02,     // native中的局部变量    
  HPROF_ROOT_JAVA_FRAME = 0x03,    // java中的局部变量    
  HPROF_ROOT_NATIVE_STACK = 0x04,  // native中的入参和出参    
  HPROF_ROOT_STICKY_CLASS = 0x05,  // 系统类   
  HPROF_ROOT_THREAD_BLOCK = 0x06,  // 活动线程引用的对象    
  HPROF_ROOT_MONITOR_USED = 0x07,  // 调用wait、nitify、synchronized的对象    
  HPROF_ROOT_THREAD_OBJECT = 0x08,     // 活动现场    
  HPROF_CLASS_DUMP = 0x20,             // 类    
  HPROF_INSTANCE_DUMP = 0x21,          // 实例对象    
  HPROF_OBJECT_ARRAY_DUMP = 0x22,      // 对象数组    
  HPROF_PRIMITIVE_ARRAY_DUMP = 0x23,   // 基础类型数组    
  // Android.    
  HPROF_HEAP_DUMP_INFO = 0xfe,         //    
  HPROF_ROOT_INTERNED_STRING = 0x89,//调用String.intern()的对象
  HPROF_ROOT_FINALIZING = 0x8a, // 等待finalizer调用的对象.    
  HPROF_ROOT_DEBUGGER = 0x8b, // 用于连接debugger的对象   
  HPROF_ROOT_REFERENCE_CLEANUP = 0x8c, // .    
  HPROF_ROOT_VM_INTERNAL = 0x8d,    
  HPROF_ROOT_JNI_MONITOR = 0x8e,    
  HPROF_UNREACHABLE = 0x90,     
  HPROF_PRIMITIVE_ARRAY_NODATA_DUMP = 0xc3,  // Obsolete.
};

三、代码层面分析hprof

接下来的代码非常的长, 只截取部分代码进行简短说明. 代码表达的流程如下:

  • 1、打开hprof文件, 读取文件头信息
  • 2、解析文件, 创建HprofHeapGraph保存指定TAG的Record信息到gcRoots(GC Roots信息)、objects(内存中对象信息)、classes(类信息)、instances(实例信息)集合中
  • 3、匹配待检测对象和HprofHeapGraph.objects找到对应对象ID
  • 4、从GC Roots开始通过BFS查找到检测对象的调用路径, 期间会根据LeakCanaryCore自带的白名单剔除名单中的类
  • 5、从众多路径中找到一条最短引用的路径, 将结果保存在HeapAnalysisSuccess中返回.
3.1 HeapAnalyzer.runAnalysis

HeapAnalyzer.runAnalysis为hprof文件分析的入口, 会触发HeapAnalyzerService.onHandleIntentInForeground的执行, HeapAnalyzer在:leakcanary进程, 所以LeakCanary HPROF文件的分析是在另外一个进程中执行

3.2 HeapAnalyzer.analyze

中间省略掉不重要的方法调用

fun analyze(
  heapDumpFile: File,
  leakingObjectFinder: LeakingObjectFinder,
  referenceMatchers: List<ReferenceMatcher> = emptyList(),
  computeRetainedHeapSize: Boolean = false,
  objectInspectors: List<ObjectInspector> = emptyList(),
  metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
  proguardMapping: ProguardMapping? = null
): HeapAnalysis {
  //1.打开HPROF文件, 读取文件头
  Hprof.open(heapDumpFile)
      .use { hprof ->
         //2.生成hprof文件中Record关系图, 用HprofHeapGraph对象保存
         val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
         val helpers =
            FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
         //3.构建从GC Roots到检测对象的最短引用路径, 并返回结果
         helpers.analyzeGraph(
              metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
        )
      }
}

备注 : 接下来的流程非常的长, 但是如果了解GC相关的知识, 代码长归长, 但是里面的逻辑其实也不算复杂, 就是围绕几个GC的概念展开进行

3.3 HprofHeapGraph.indexHprof

生成hprof文件中的Record关系图, 用HprofHeapGraph对象保存

fun indexHprof(
  hprof: Hprof,
  proguardMapping: ProguardMapping? = null,
  //indexedGcRootTypes表示可以作为GC Roots的集合
  indexedGcRootTypes: Set<KClass<out GcRoot>> = setOf(
      // native中的全局变量
      JniGlobal::class,
      // java中的局部变量
      JavaFrame::class,
      // native中的局部变量
      JniLocal::class,
      // 调用wait、notify、synchronized的对象
      MonitorUsed::class,
      // native中的入参和出参
      NativeStack::class,
      // 系统类
      StickyClass::class,
      // 活动线程引用的对象
      ThreadBlock::class,
      // 活动线程      
      ThreadObject::class,
      JniMonitor::class
  )
): HeapGraph {
  val index = HprofInMemoryIndex.createReadingHprof(hprof, proguardMapping, indexedGcRootTypes)
  return HprofHeapGraph(hprof, index)
}
3.4 HprofInMemoryIndex.createReadingHprof

流程: 接下来从hprof文件读取records, 这个读取过程就是按照hprof文件格式进行读取了, 通过接下来的代码分析可知LeakCanary只会读取以下几种类型的record:

  • STRING IN UTF-8
    0x01: UTF8格式的字符串
  • LOAD CLASS
    0x02: 虚拟机中加载的类
  • HEAP DUMP中的CLASS DUMP
    0x0C->0x20: dump出来内存中的类实例
  • HEAP DUMP中的INSTANCE DUMP
    0x0C->0x21: dump出来内存中的对象实例
  • HEAP DUMP中的OBJECT ARRAY DUMP
    0x0C->0x22: dump出来的内存中的对象数组实例
  • HEAP DUMP中的PRIMITIVE ARRAY DUMP
    0x0C->0x23: dump出来内存中的原始类型数组实例
  • HEAP中的GC Roots

在这里插入图片描述

接下来只截取readHprofRecords中的一段代码, 以InstanceSkipContentRecord为例,其调用链为:

HprofReader.readInstanceDumpRecord()
->HprofReader.readInstanceSkipContentRecord()

在这里插入图片描述
上面这段代码对应HPROF Agent中的0x21中的内容:在这里插入图片描述

接下来的代码分析不下去了啊, 哎, 实在是太复杂了, 太耗精力了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值