LeakCanary源码分析以及ContentProvider的优化方案,面试几个月还没找到工作

.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
// Hack. We don’t have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}

可以看到,它使用了Runtime.getRuntime().gc()而不是System.gc(),进入System.gc源码一看

public static void gc() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = justRanFinalization;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}

可以看到System.gc源码的还是最终实现是Runtime.getRuntime().gc();但是需要一系列的判断条件,我们手动调用System.runFinalization()可以使gc方法中的justRanFinalizationw为true,从而保证Runtime.getRuntime().gc()会被执行。

####3.如何判断对象可能泄露:ReferenceQueue含义及作用

在Activity/Fragment销毁后,会进行一系列的对象回收,我们把这些对象分别和引用队列进行关联,当某个对象被回收时,**(弱引用一旦变成弱可达(可达性算法分析),引用就会加到引用队列中,然后再进行回收)**我们对象的引用就会被加入到引用队列中。根据该原理进行一系列的操作,最终判断是否内存泄漏。

#####3.1 引用队列

通常我们将其ReferenceQueue翻译为引用队列,换言之就是存放引用的队列,保存的是Reference对象。其作用在于Reference对象所引用的对象被GC回收时,该Reference对象将会被加入引用队列中(ReferenceQueue)的队列末尾。

ReferenceQueue常用的方法:

public Reference poll():从队列中取出一个元素,队列为空则返回null;

public Reference remove():从队列中出对一个元素,若没有则阻塞至有可出队元素;

public Reference remove(long timeout):从队列中出对一个元素,若没有则阻塞至有可出对元素或阻塞至超过timeout毫秒;

  1. 强引用

  2. 软引用

  3. 弱引用

  4. 虚引用(Phantom Reference)

虚引等同于没有引用,这意味着在任何时候都可能被GC回收,设置虚引用的目的是为了被虚引用关联的对象在被垃圾回收器回收时,能够收到一个系统通知。(被用来跟踪对象被GC回收的活动)虚引用和弱引用的区别在于:虚引用在使用时必须和引用队列(ReferenceQueue)联合使用,其在GC回收期间的活动如下:

ReferenceQueue queue=new ReferenceQueue();

PhantomReference pr=new PhantomReference(object,queue);

也即是GC在回收一个对象时,如果发现该对象具有虚引用,那么在回收之前会首先该对象的虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入虚引用来了解被引用的对象是否被GC回收。

#####3.2 GC Root对象

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#####3.3 内存是否泄漏

知道引用队列的原理后,先大概描述一下如何判断是否泄漏,首先创建三个队列

/**

  • References passed to [watch] that haven’t made it to [retainedReferences] yet.
  • watch() 方法传进来的引用,尚未判定为泄露
    /
    private val watchedReferences = mutableMapOf<String, KeyedWeakReference>()
    /
    *
  • References passed to [watch] that we have determined to be retained longer than they should
  • have been.
  • watch() 方法传进来的引用,已经被判定为泄露
    */
    private val retainedReferences = mutableMapOf<String, KeyedWeakReference>()
    private val queue = ReferenceQueue() // 引用队列,配合弱引用使用

//KeyedWeakReference,对象和引用队列进行弱引用关联,所以这个对象一定会被回收
class KeyedWeakReference(
referent: Any,
val key: String,
val name: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue
) : WeakReference(
referent, referenceQueue
) {
@Volatile
var retainedUptimeMillis = -1L

companion object {
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L
}

}

如果一个obj对象,它和队列queue进行弱引用关联,在进行垃圾收集时,发现该对象具有弱引用,会把引用加入到引用队列中,我们如果在该队列中拿到引用,则说明该对象被回收了,如果拿不到,则说明该对象还有强/软引用未释放,那么就说明对象还未回收,发生内存泄漏了,然后dump内存快照,使用第三方库进行引用链分析

这里重点强调一点一个对象可能被多个引用持有,比如强引用,软引用,弱引用,只要这个对象还有强引用/软引用,与这个对象关联的任意引用队列就拿不到引用,引用队列就相当于一个通知,多个引用队列和一个对象关联,对象被回收时,多个队列都会受到通知

#####3.4 watch()

@Synchronized fun watch(
watchedReference: Any,
referenceName: String
) {
if (!isEnabled()) {
return
}
//移除队列中将要被 GC 的引用
removeWeaklyReachableReferences()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference = // 构建当前引用的弱引用对象,并关联引用队列 queue
KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
if (referenceName != “”) {
CanaryLog.d(
“Watching instance of %s named %s with key %s”, reference.className,
referenceName, key
)
} else {
CanaryLog.d(
“Watching instance of %s with key %s”, reference.className, key
)
}

watchedReferences[key] = reference
checkRetainedExecutor.execute {
//如果引用未被移除,则可能存在内存泄漏
moveToRetained(key)
}
}

removeWeaklyReachableReferences()

private fun removeWeaklyReachableReferences() {
// 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.
// 弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference? // 队列 queue 中的对象都是会被 GC 的
if (ref != null) {
val removedRef = watchedReferences.remove(ref.key)
if (removedRef == null) {
retainedReferences.remove(ref.key)
}
// 移除 watchedReferences 队列中的会被 GC 的 ref 对象,剩下的就是可能泄露的对象
}
} while (ref != null)
}

moveToRetained()

@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableReferences() // 再次调用,防止遗漏
val retainedRef = watchedReferences.remove(key)
if (retainedRef != null) {
retainedReferences[key] = retainedRef
onReferenceRetained()
}
}

最后会回调到InternalLeakCanary的onReferenceRetained()方法

override fun onReferenceRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onReferenceRetained()
}
}

//1.HeapDumpTrigger 的 onReferenceRetained()
fun onReferenceRetained() {
scheduleRetainedInstanceCheck(“found new instance retained”)
}

//2.scheduleRetainedInstanceCheck
private fun scheduleRetainedInstanceCheck(reason: String) {
backgroundHandler.post {
checkRetainedInstances(reason)
}
}

//3.checkRetainedInstances
private fun checkRetainedInstances(reason: String) {
CanaryLog.d(“Checking retained instances because %s”, reason)
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
return
}

var retainedKeys = refWatcher.retainedKeys
//当前泄露实例个数小于 5 个,不进行 heap dump
if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
showRetainedCountWithDebuggerAttached(retainedKeys.size)
scheduleRetainedInstanceCheck(“debugger was attached”, WAIT_FOR_DEBUG_MILLIS)
CanaryLog.d(
“Not checking for leaks while the debugger is attached, will retry in %d ms”,
WAIT_FOR_DEBUG_MILLIS
)
return
}
// 可能存在被观察的引用将要变得弱可达,但是还未入队引用队列。
// 这时候应该主动调用一次 GC,可能可以避免一次 heap dump
gcTrigger.runGc()

retainedKeys = refWatcher.retainedKeys

if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

HeapDumpMemoryStore.setRetainedKeysForHeapDump(retainedKeys)

CanaryLog.d(“Found %d retained references, dumping the heap”, retainedKeys.size)
HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis()
dismissNotification()
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
CanaryLog.d(“Failed to dump heap, will retry in %d ms”, WAIT_AFTER_DUMP_FAILED_MILLIS)
scheduleRetainedInstanceCheck(“failed to dump heap”, WAIT_AFTER_DUMP_FAILED_MILLIS)
showRetainedCountWithHeapDumpFailed(retainedKeys.size)
return
}

refWatcher.removeRetainedKeys(retainedKeys)

HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}

一些细节可以看看代码注释,checkRetainedCount满足个数的话,就要发起head dump,具体的逻辑在AndroidHeapDumper.dumpHeap()中:

override fun dumpHeap(): File? {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
···
return try {
//Dump出文件
Debug.dumpHprofData(heapDumpFile.absolutePath)
heapDumpFile
} catch (e: Exception) {
CanaryLog.d(e, “Could not dump heap”)
// Abort heap dump
null
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}

最后启动一个前台服务 HeapAnalyzerService 来分析 heap dump 文件。老版本中是使用 Square 自己的 haha 库来解析的,这个库已经废弃了,Square 完全重写了解析库,主要逻辑都在 moudle leakcanary-analyzer 中。这部分我还没有阅读,就不在这里分析了。对于新的解析器,官网是这样介绍的:

Uses 90% less memory and 6 times faster than the prior heap parser.

减少了 90% 的内存占用,而且比原来快了 6 倍。后面有时间单独来分析一下这个解析库。

后面的过程就不再赘述了,通过解析库找到最短 GC Roots 引用路径,然后展示给用户。

4.手动写内存泄漏检测

下面是参考Zero的Demo写的内存泄漏检测的一个例子,思路和LeakCanary一样

fun main() {

class MyKeyedWeakReference(
referent: Any,
val key: String,
val name: String,
referenceQueue: ReferenceQueue
) : WeakReference(
referent, referenceQueue
) {
val className: String = referent.javaClass.name
override fun toString(): String {
return “{key= k e y , c l a s s N a m e = key,className= key,className=className}”
}
}
//需要观察的对象
val watchedReferences = mutableMapOf<String,MyKeyedWeakReference>()
//如果最后retainedReferences还存在引用,说明泄漏了
val retainedReferences = mutableMapOf<String,MyKeyedWeakReference>()
//当与之关联的弱引用中的实例被回收,则会加入到queue
val gcQueue = ReferenceQueue()

fun sleep(mills: Long){
try {
Thread.sleep(mills)
}catch (e: Exception){
e.printStackTrace()
}
}

fun gc(){
println(“执行gc…”)
Runtime.getRuntime().gc()
sleep(100)
System.runFinalization()
}

fun removeWeaklyReachableReferences(){
println(“removeWeaklyReachableReferences”)
var ref: MyKeyedWeakReference?
do {
ref = gcQueue.poll() as MyKeyedWeakReference? //队列queue中的对象都是会被GC的
println(“ref=KaTeX parse error: Expected '}', got 'EOF' at end of input: …了 println("ref=ref, 对象被释放了,key= r e f . k e y " ) v a l r e m o v e d R e f = w a t c h e d R e f e r e n c e s . r e m o v e ( r e f . k e y ) p r i n t l n ( " r e m o v e d R e f = {ref.key}") val removedRef = watchedReferences.remove(ref.key) println("removedRef= ref.key")valremovedRef=watchedReferences.remove(ref.key)println("removedRef=removedRef, 如果removedRef为null,说明已经不在watchedReferences了,key=${ref.key}”)
if (removedRef == null){
//不在watchedReferences则说明在retainedReferences
retainedReferences.remove(ref.key)
}
}
}while (ref != null)
}

@Synchronized
fun moveToRetained(key: String){
println(“5.moveToRetained,key= k e y " ) r e m o v e W e a k l y R e a c h a b l e R e f e r e n c e s ( ) v a l r e t a i n e d R e f = w a t c h e d R e f e r e n c e s . r e m o v e ( k e y ) p r i n t l n ( " r e t a i n e d R e f = key") removeWeaklyReachableReferences() val retainedRef = watchedReferences.remove(key) println("retainedRef = key")removeWeaklyReachableReferences()valretainedRef=watchedReferences.remove(key)println("retainedRef=retainedRef 如果还有值说明没有被释放”)
if (retainedRef != null){ //添加到retainedReferences
retainedReferences[key] = retainedRef
}

}

fun watch(

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-mzPxQ0rU-1711779194830)]

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-JFHFzE82-1711779194830)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值