Hi,各位朋友们,笨鸟又跟大家见面啦。之后的一两个月里,小笨鸟将会聚焦于Android性能优化这个方面,推出一系列的文章。做为android进阶知识,性能优化不论是在社招面试仍是在平常工做中都是至关实用的知识,而且也是区分中级和高级程序员的试金石。小笨鸟就会以不一样的专题来进行讲解,但愿你们喜欢,若是想了解更多的话,欢迎关注我一块儿学习。html
今天咱们先学习内存优化中的一个小知识点,就是内存泄露的检测和解决。固然,如何解决是大多数中级工程师都要去学习的东西,网上也有大量的资料,因此我这里不会详解。而是主要着眼于内存泄露的检测。Square公司出品的大名鼎鼎的LeakCanary,就是业界知名的内存泄露检测的利器。java
阅读本篇文章,预计须要20分钟,你将会学习到:android
关于LeakCanary的原理,官网上已经给出了详细的解释。翻译过来就是: 1.LeakCanary使用ObjectWatcher来监控Android的生命周期。当Activity和Fragment被destroy之后,这些引用被传给ObjectWatcher以WeakReference的形式引用着。若是gc完5秒钟之后这些引用尚未被清除掉,那就是内存泄露了。
2.当被泄露掉的对象达到一个阈值,LeakCanary就会把java的堆栈信息dump到.hprof
文件中。
3.LeakCanary用Shark库来解析.hprof
文件,找到没法被清理的引用的引用栈,而后再根据对Android系统的知识来断定是哪一个实例致使的泄露。
4.经过泄露信息,LeakCanary会将一条完整的引用链缩减到一个小的引用链,其他的由于这个小的引用链致使的泄露链都会被聚合在一块儿。git
经过官网的介绍,咱们很容易就抓住了学习LeakCanary这个库的重点:程序员
.hprof
文件的?看官方原理老是感受不过瘾,下面咱们从代码层面上来分析。本文基于LeakCanary 2.0 beta版。github
LeakCanary的使用至关的简单。只须要在module的build.gradle添加一行依赖,代码侵入少。面试
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3'
}
复制代码
就这样,应用很是简单就接入了LeakCanary内存检测功能。固然还有一些更高级的用法,好比更改自定义config,增长监控项等,你们能够参考官网编程
和以前的1.x版本相比,2.0甚至都不须要再在Application里面增长install的代码。可能不少的同窗都会疑惑,LeakCanary是如何插入本身的初始化代码的呢? 其实这里LeakCanary是使用了ContentProvider来进行初始化。我以前在介绍Android插件化系列三:技术流派和四大组件支持的时候曾经介绍过ContentProvider的特色,即在打包的过程当中来自不一样module的ContentProvider最后都会merge到一个文件中,启动app的时候ContentProvider是自动安装,而且安装会比Application的onCreate还早。LeakCanary就是依据这个原理进行的设计。具体能够参考【译】你的Android库是否还在Application中初始化?性能优化
咱们能够查看LeakCanary源码,发现它在leakcanary-object-watcher-android
的AndroidManifest.xml
中有一个ContentProvider。bash
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
复制代码
而后咱们查看AppWatcherInstaller的代码,发现内部是使用InternalAppWatcher进行的install。
// AppWatcherInstaller
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
InternalAppWatcher.install(application)
return true
}
// InternalAppWatcher
fun install(application: Application) {
// 省略部分代码
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config }
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
}
复制代码
能够看到这里主要把Activity和Fragment区分了开来,而后分别进行注册。Activity的生命周期监听是借助于Application.ActivityLifecycleCallbacks。
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(activity)
}
}
}
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
复制代码
而Fragment的生命周期监听是借助了Activity的ActivityLifecycleCallbacks生命周期回调,当Activity建立的时候去调用FragmentManager.registerFragmentLifecycleCallbacks方法注册Fragment的生命周期监听。
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(view)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(fragment)
}
}
}
复制代码
最终,Activity和Fragment都将本身的引用传入了ObjectWatcher.watch()进行监控。从这里开始进入到LeakCanary的引用监测逻辑。
题外话:LeakCanary 2.0版本和1.0版本相比,增长了Fragment的生命周期监听,每一个类的职责也更加清晰。可是我我的以为使用 (Activty)->Unit 这种lambda表达式做为类的写法不是很优雅,倒不如面向接口编程。彻底能够设计成ActivityWatcher和FragmentWatcher都继承自某个接口,这样也方便后续扩展。
1.引用
首先咱们先介绍一点准备知识。你们都知道,java中存在四种引用:
一个对象在被gc的时候,若是发现还有软引用(或弱引用,或虚引用)指向它,就会在回收对象以前,把这个引用加入到与之关联的引用队列(ReferenceQueue)中去。若是一个软引用(或弱引用,或虚引用)对象自己在引用队列中,就说明该引用对象所指向的对象被回收了。
当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象自己就没有价值了,若是程序中存在大量的这类对象(注意,咱们建立的软引用、弱引用、虚引用对象自己是个强引用,不会自动被gc回收),就会浪费内存。所以咱们这就能够手动回收位于引用队列中的引用对象自己。
好比咱们常常看到这种用法
WeakReference<ArrayList> weakReference = new WeakReference<ArrayList>(list);
复制代码
还有也有这样一种用法
WeakReference<ArrayList> weakReference = new WeakReference<ArrayList>(list, new ReferenceQueue<WeakReference<ArrayList>>());
复制代码
这样就能够把对象和ReferenceQueue关联起来,进行对象是否gc的判断了。另外咱们从弱引用的特征中看到,弱引用是不会影响到这个对象是否被gc的,很适合用来监控对象的gc状况。
2.GC
java中有两种手动调用GC的方式。
System.gc();
// 或者
Runtime.getRuntime().gc();
复制代码
咱们在第一节中提到,Activity和Fragment都依赖于响应的LifecycleCallback来回调销毁信息,而后调用了ObjectWatcher.watch添加了销毁后的监控。接下来咱们看ObjectWatcher.watch作了什么操做。
@Synchronized fun watch( watchedObject: Any, name: String ) {
removeWeaklyReachableObjects()
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
复制代码
这里咱们看到,有一个存储着KeyedWeakReference的ReferenceQueue对象。在每次增长watch object的时候,都会去把已经处于ReferenceQueue中的对象给从监控对象的map即watchObjects中清理掉,由于这些对象都已经被回收了。而后再去生成一个KeyedWeakReference,这个对象就是一个持有了key和监测开始时间的WeakReference对象。 最后再去调用moveToRetained,至关于记录和回调给监控方这个对象正式开始监测的时间。
那么咱们如今已经拿到了须要监控的对象了,可是又是怎么去判断这个对象已经内存泄露的呢?这就要继续往下面看。咱们主要到前面在讲解InternalAppWatcher的install方法的时候,除了install了Activity和Fragment的检测器,还调用了onAppWatcherInstalled(application)方法,看代码发现这个方法就是InternalLeakCanary的invoke方法。
override fun invoke(application: Application) {
this.application = application
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
}
override fun onObjectRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onObjectRetained()
}
}
复制代码
咱们看到首先是初始化了heapDumper,gcTrigger,heapDumpTrigger等对象用于gc和heapDump,同时还实现了OnObjectRetainedListener,并把本身添加到了上面的onObjectRetainedListeners中,以便每一个对象moveToRetained的时候,InternalLeakCanary都能获取到onObjectRetained()的回调,回调里就只是回调了heapDumpTrigger.onObjectRetained()方法。看来都是依赖于HeapDumpTrigger这个类。
HeapDumpTrigger主要的处理逻辑都在checkRetainedObjects方法中。
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc() // 触发一次GC操做,只保留不能被回收的对象
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
showRetainedCountWithDebuggerAttached(retainedReferenceCount)
scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
return
}
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
dismissRetainedCountNotification()
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
scheduleRetainedObjectCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
showRetainedCountWithHeapDumpFailed(retainedReferenceCount)
return
}
lastDisplayedRetainedObjectCount = 0
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
复制代码
那么HeapDumpTrigger具体作了些啥呢?我理了一下主要是下面几个功能:
看到了这里,咱们应该脑海中有概念了。Activity和Fragment经过注册系统的监听在onDestroy的时候把本身的引用放入ObjectWatcher进行监测,监测主要是经过HeapDumpTrigger类轮询进行,主要是调用AndroidHeapDumper来dump出文件来,而后依赖于HeapAnalyzerService来进行分析。后面一小节,咱们将会聚焦于对象dump操做和HeapAnalyzerService的分析过程。
hprof是JDK提供的一种JVM TI Agent native工具。JVM TI,全拼是JVM Tool interface,是JVM提供的一套标准的C/C++编程接口,是实现Debugger、Profiler、Monitor、Thread Analyser等工具的统一基础,在主流Java虚拟机中都有实现。hprof工具事实上也是实现了这套接口,能够认为是一套简单的profiler agent工具。咱们在新知周推:10.8-10.14(启动篇)中也提到过,能够参考其中美团的文章。
用过Android Studio Profiler工具的同窗对hprof文件都不会陌生,当咱们使用Memory Profiler工具的Dump Java heap图标的时候,profiler工具就会去捕获你的内存分配状况。可是捕获之后,只有在Memory Profiler正在运行的时候咱们才能查看,那么咱们要怎么样去保存当时的内存使用状况呢,又或者我想用别的工具来分析堆分配状况呢,这时候hprof文件就派上用场了。Android Studio能够把这些对象给export到hprof文件中去。
LeakCanary也是使用的hprof文件进行对象存储。hprof文件比较简单,总体按照 前置信息 + 记录表的格式来组织的。可是记录的种类至关之多。具体种类能够查看HPROF Agent。
同时,android中也提供了一个简便的方法Debug.dumpHprofData(filePath)能够把对象dump到指定路径下的hprof文件中。LeakCanary使用使用Shark库来解析Hprof文件中的各类record,比较高效,使用Shark中的HprofReader和HprofWriter来进行读写解析,获取咱们须要的信息。你们能够关注一些比较重要的,好比:
dump具体的代码在AndroidHeapDumper类中。HprofReader和HprofWriter过于复杂,有兴趣的直接查看源码吧
override fun dumpHeap(): File? {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
return try {
Debug.dumpHprofData(heapDumpFile.absolutePath)
if (heapDumpFile.length() == 0L) {
null
} else {
heapDumpFile
}
} catch (e: Exception) {
null
} finally {
}
}
复制代码
前面咱们已经分析到了,HeapDumpTrigger主要是依赖于HeapAnalyzerService进行分析。那么这个HeapAnalyzerService究竟有什么玄机?让咱们继续往下面看。能够看到HeapAnalyzerService实际上是一个ForegroundService。在接收到分析的Intent后就会调用HeapAnalyzer的analyze方法。因此最终进行分析的地方就是HeapAnalyzer的analyze方法。
核心代码以下
try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
Hprof.open(heapDumpFile)
.use { hprof ->
// 1.生成graph
val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
// 2.寻找Leak
val findLeakInput = FindLeakInput(
graph, leakFinders, referenceMatchers, computeRetainedHeapSize, objectInspectors
)
val (applicationLeaks, libraryLeaks) = findLeakInput.findLeaks()
listener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
return HeapAnalysisSuccess(
heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
applicationLeaks, libraryLeaks
)
}
} catch (exception: Throwable) {
listener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
return HeapAnalysisFailure(
heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
HeapAnalysisException(exception)
)
}
}
复制代码
这段代码中涉及到了专为LeakCanary设计的Shark库的用法,在这里就很少解释了。大概介绍一下每一步的做用:
本篇文章分析了LeakCanary检测内存泄露的思路和一些代码的设计思想,可是限于篇幅不能面面俱到。接下来咱们回答一下文章开头提出的问题。
1.LeakCanary是如何使用ObjectWatcher 监控生命周期的?
LeakCanary使用了Application的ActivityLifecycleCallbacks和FragmentManager的FragmentLifecycleCallbacks方法进行Activity和Fragment的生命周期检测,当Activity和Fragment被回调onDestroy之后就会被ObjectWatcher生成KeyedReference来检测,而后借助HeapDumpTrigger的轮询和触发gc的操做找到弹出提醒的时机。
2.LeakCanary如何dump和分析.hprof
文件的?
使用Android平台自带的Debug.dumpHprofData方法获取到hprof文件,使用自建的Shark库进行解析,获取到LeakTrace
【译】你的Android库是否还在Application中初始化?
我是Android笨鸟之旅,笨鸟也要有向上飞的心,我在这里陪你一块儿慢慢变强。