Android内存泄露分析笔记

很久以前就谋划着做个学习笔记,但由于懒癌晚期,一直都没能提起干劲儿开始它,但这都快要过年了,不能让这个问题留着过年,今天必须得要写下去。所谓问题的分析就得要用分析问题的方式。

零、大纲

  1. 问题:内存泄露带来的问题。
  2. 原因:为何会造成内存泄露。
  3. 解决方法
    分析工具:Android Profiler
    常见的示例

一、问题

现象

内存泄露,即系统分配的内存需要被回收时不能被及时回收,导致内存使用不断累积,这很容易造成OOM(内存溢出)。

二、原因

如上所说,内存泄露是由于该回收时无法回收造成的,那么回收是谁来回收,什么时候需要回收呢?

1. 原理知识

谁来回收--java内存分配策略(栈与堆)

在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。栈内存随着作用域范围来进行自动释放内存。
堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组。堆内存由java的垃圾回收器GC(Garbage Collection)来管理,它决定是否释放内存。

什么能够回收--java回收机制(无引用)

释放对象的根本原则就是该对象不再被引用(java有四种引用方式,其中强引用影响回收)。
有向图,监管对象是否被引用。每一个对象不能从GC根节点到达,则GC会将其回收。


有向图.png

触发方式

一般在Andriod中,Activity和Fragment作为界面的载体必不可少,而资源的创建与释放也通常以这两个作为界限。而Activity的创建,并不像一个对象那样直接被new出来的,也不会有Activity直接置空来进行释放,而是通过生命周期来管理的。若一个Activity在销毁后仍然有对象对其持有引用,那么GC是不会将其回收的,这也就造成了Activity中分配的内存得不到释放。Fragment也是同理。

三、解决方法

分析工具

网络上常见的分析工具有MAT「Memory AnalysisTools」、LeakCanary以及将要着重介绍的Android Studio自带工具性能分析器--Android Profiler。

Android Profiler

  • 视图介绍


    Android Profiler.png

    连接上设备后(这里我使用了模拟器),就可以看到有这个选项了,点开后就可以查看提供的各个性能记录了。


    性能监视视图窗口.png

    ①为所选中查看的设备。
    ②为选择你所要查看的调试应用进程。

    ③为性能监视视图窗口,依次为:事件记录、CPU使用情况记录、内存使用情况记录和网络使用情况记录。其中我们所要关注的是内存使用记录情况(MEMORY),用来分析内存泄露。
    双击内存记录区域后,将会看到详细的内存使用情况。


    内存操作按钮.png
  • 内存操作按钮
    ①强制GC,主动释放内存。
    ②捕获堆转储。
    ③记录内存分配(高版本API8.0以上没有此按钮)。

介绍到这里,暂时回过头来想想,通过上面的原理可以知道,如果我们在一个Activity或某个活动后需要释放某个对象所占用的内存,如果这个对象在GC后仍然存在,那么就可以说发生内存泄露了,我们就得需要查看到底是谁在持续地引用这个对象导致没有成功的释放。

实例演示

在这里,我们以常见的Handler引起的内存泄露作为例子来说明。

public class HandlerLeakActivity extends AppCompatActivity {
    private static final String TAG = "HandlerLeakActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_leak);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "持续引用结束!");
            }
        }, 1000 * 100);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        finish();
    }
}

这里我创建了一个匿名内部类Handler,并使用Handler延迟10秒执行,这样由于匿名内部类会持有外部引用,所以在这100秒内按返回键销毁此Activity时是无法释放该Activity的。
我们这里在100秒内按返回后,使用强制GC按钮,并捕获堆转储,查看当前内存分配情况。


查看分类.png

①导出HProf文件,如用MAT进行分析。
②Heap 共分为:
zygote heap: 与 android zygote 进程共享的 heap
image heap: 与系统共享的 heap
app heap: 应用本身的 heap
③分类查看实例分配信息,这里为方便定位,使用按包名查看即可。

引用对象查看.png

①通过包名一路查看下来,果然在我们退出HandlerLeakActivity后并没有被GC掉,其中这个"$"代表类中创建的类,即我们的内部类也仍然存在。我点进去后继续查看。
②这里查看选中的实例,继续双击,看有哪些在持有它。
③其中我们发现了熟悉的身影,其中在HandlerLeakActivity的内部类对它进行了持有,而在①中也看到它仍然存在,所以正因为它的持续引用HandlerLeakActivity,造成了HandlerLeakActivity的释放不及时。

找到了原因后,我们该怎么避免内存泄露呢?我们可以使用静态类替换使用内部类来避免对外部类的持有。这里由于100秒已经属于很长的耗时行为了,所以不仅要避免持有,我们还得要及时地中断这个耗时行为(这里及时清空消息,也可以避免持续持有,这两个方法都能避免内存泄露)。

public class HandlerLeakActivity extends AppCompatActivity {
    private static final String TAG = "HandlerLeakActivity";
    DelayHandler mDelayHandler = new DelayHandler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_leak);

        mDelayHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "持续引用结束!");
            }
        }, 1000 * 100);

//        new Handler().postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                Log.i(TAG, "持续引用结束!");
//            }
//        }, 1000 * 100);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        //清空Message,避免有耗时行为存在
        mDelayHandler.removeCallbacksAndMessages(null);
        finish();
    }

    static class DelayHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}

再次按原来的操作进行,查看内存分配。

图片.png

果然如我们所预料的那样,HandlerLeakActivity被及时释放掉,没有这个实例存在了。是不是对内存泄露分析不再那么头大了?-

  • 这里解释一下右边的几个参数:
    Allocations:实例个数。
    depth:从任何GC根到所选实例的跳数最短。
    native size:8.0之后的手机会显示,主要反映Bitmap所使用的像素内存(8.0之后,转移到了native)
    Shallow size:就是对象本身占用内存的大小,不包含其引用的对象。
    retained size:指包含该对象及其引用对象所占用的内存大小,是该对象被GC之后所能回收到内存的总和。

常见的内存泄露

  1. 内部类
    问题:内部类对外部类持有。
    方案:将内部类改为静态类。
  2. 订阅事件
    问题:订阅事件耗时操作,对其持有。
    方案:及时解除订阅,一般在OnDestroy()的生命周期中执行。
  3. Context
    问题:使用到Activity的context的耗时操作。
    方案:可用getApplicationContext()来替代该Activity的context。
  4. 引用(强引用、弱引用和软引用...)
    问题:某些实例需要被GC及时回收,但强引用无法被及时回收掉。
    方案:使用弱引用或软引用来获取该实例,并注意判空后再使用。
    注意的是,引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。
Fragment fragment = new Fragment();
WeakReference reference = new WeakReference(fragment);
Fragment weakFragment = (Fragment) reference.get();

5.待补充啦....

PS:总算填坑完成了,第一次写笔记难免有所疏漏,若有哪些不对的地方还望指正。

参考文章:
Java中关于内存泄漏出现的原因以及如何避免内存泄漏
Java堆:Shallow Size和Retained Size
AndroidStudio3.0最新 Android Profiler分析器(cpu memory network 分析器)
Android Studio 中的 HProf静态分析
Java 如何有效地避免OOM:善于利用软引用和弱引用

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容