不要记源码细节, 记也记不住, 记涉及到的基础知识、学习思想、学习架构、学习用到的哪些设计模式
一、 监控内存泄漏
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字节组成) |
u4 | size 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中的内容:
接下来的代码分析不下去了啊, 哎, 实在是太复杂了, 太耗精力了.