Android内存泄漏原因

2022-10-26  本文已影响0人  大公爵

一、Android内存泄漏的本质原因

我们经常看到的说法是,安卓内存泄漏是因为长生命周期的对象持有了短生命周期的引用导致本应该本回收的内存无法回收
但是什么是长生命周期呢,正常我们知道单例、Application、static是长生命周期,但是为什么Handler也会造成内存泄漏,Handler和这三种情况没有什么关系

所以实际上造成内存泄漏的原因是,本来应该被回收的内存因为被GCRoot树里面的变量持有导致的
也就是说我们只要知道什么是GCRoot树,GCRoot根,就能举一反三快速理解某些内存泄漏发生的原因了

二、什么是GCRoot树和GCRoot根

GCRoot根有以下几种

被GCRoot根引用的对象会虚拟机判定为不可回收对象,也就是GC时不做回收处理。
而GCRoot树就是被这些GCRoot根引用的一系列引用的集合,比如一个存活的线程中如果调用了Activity,那么这个Activity就无法被Thread回收。

三、有哪几种内存泄漏的情况

四、下面具体说明一下某些情况内存泄漏的情况和GCRoot根的关系

1、属性动画为什么会导致内存泄漏?

属性动画是因为动画框架里面有一个单例的AnimationHandler,是使用了static,这个单例传入了一个AnimationCallback,这个Callback是一个接口,而ValueAnimation是这个接口的实现类,也就是单例持有了属性动画的引用,属性动画又持有外部View的引用,View又持有Activity引用,就导致了Activity最终无法被回收,这里一定要自己去研究一下属性动画的源码,才能真正的理解。

2、为什么Handler会导致内存泄漏?

先说为什么只有匿名内部类的Handler和具名Handler的handleMessage方法里面持有外部引用的时候才会导致内存泄漏,因为这两种情况Handler都持有外部类引用,常见的是一个Activity。
那么Activity被Handler持有,Handler被Message持有(Message.target),Message被MQ持有,MQ又被Looper持有,Looper被线程持有,Looper又是线程里面的死循环,不会主动退出,而活跃的线程是GCRoot根,这就导致了Handler方法里面引用在线程死掉之前都不会被释放的。
所以这里可以联想到为什么我们在处理AsyncTask的内存泄漏的时候,有一种处理方式是使用静态内部类的方式。

3、Kotlin的object为什么会导致内存泄漏?

在对Kotlin类使用object的时候,其实本质就是创建了一个饿汉模式的单例类,而这个饿汉模式的单例是通过static创建的,也就是说被这个单例引用的对象都在GCRoot根上面,所以如果传入了外部引用,一定注意在合适的地方处理为空,或者改用长生命周期的内容。

4、Eventbus如果不配置unRegister为什么会造成内存泄漏?

我们知道Eventbus是通过观察者模式来实现消息传递的,我们在register的时候传入的Activity就是观察者,最终会被保存在一个static的Map中,当Eventbus接收到消息的时候,会遍历所有观察者匹配参数类型发送消息。这种情况下,如果一个Activity退出页面了,但是没有调用unRegister,还被Eventbus持有,就会导致Activity无法回收。

5、碰到内存泄漏如何处理

可以把上面内存泄漏的几种情况大概分为三类

五、结语

到这里为止,我们基本上就能自己去分析某些内存泄漏的原因,就是找GCRoot根,找到以后想办法把要回收的内存引用和GCRoot根断开。

上一篇 下一篇

猜你喜欢

热点阅读