Android Studio 使用Memory Monitor进行内存泄露分析

在使用Android Studio进行内存泄露分析以前,咱们先回顾一下Java相关的内存管理机制,而后再讲述一下内存分析工具如何使用。java

1、Java内存管理机制

1. Java内存分配策略

Java 程序运行时的内存分配策略有三种:静态分配、栈式分配和堆式分配。程序员

对应的存储区域以下:数组

  • 静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,而且在程序整个运行期间都存在。
  • 栈区 :方法体内的局部变量都在栈上建立,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。
  • 堆区 : 又称动态内存分配,一般就是指在程序运行时直接 new 出来的内存。这部份内存在不使用时将会由 Java 垃圾回收器来负责回收。

2. 堆与栈的区别

栈内存:在方法体内定义的局部变量(一些基本类型的变量和对象的引用变量)都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的做用域后,分配给它的内存空间也将被释放掉,该内存空间能够被从新使用。异步

堆内存:用来存放全部由 new 建立的对象(包括该对象其中的全部成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是咱们上面说的引用变量。咱们能够经过这个引用变量来访问堆中的对象或者数组。ide

3. Java管理内存的机制

Java的内存管理就是对象的分配和释放问题。内存的分配是由程序员来完成,内存的释放由GC(垃圾回收机制)完成。GC 为了可以正确释放对象,必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等。这是Java程序运行较慢的缘由之一。工具

1). 释放对象的原则:

该对象再也不被引用。oop

2). GC的工做原理:

将对象考虑为有向图的顶点,将引用关系考虑为有向图的有向边,有向边从引用者指向被引对象。另外,每一个线程对象能够做为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程为顶点开始的一棵根树。在有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。若是某个对象与这个根顶点不可达,那么咱们认为这个对象再也不被引用,能够被 GC 回收。
另外,Java使用有向图的方式进行内存管理,能够消除引用循环的问题,例若有三个对象相互引用,但只要它们和根进程不可达,那么GC也是能够回收它们的。固然,除了有向图的方式,还有一些别的内存管理技术,不一样的内存管理技术各有优缺点,在这里就不详细展开了。

3). Java中的内存泄漏

若是一个对象知足如下两个条件:post

(1)这些对象是可达的,即在有向图中,存在通路能够与其相连测试

(2)这些对象是无用的,即程序之后不会再使用这些对象spa

就能够断定为Java中的内存泄漏,这些对象不会被GC所回收,继续占用着内存。

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,而后却不可达,因为C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收。

2、Android中的内存泄漏

1. 单例形成的内存泄漏 

在Android开发中,常见的单例问题形成内存泄漏的场景以下:

当建立这个单例的时候,因为须要传入一个Context,因此这个Context的生命周期的长短相当重要:

1.若是此时传入的是 Application 的 Context,由于 Application 的生命周期就是整个应用的生命周期,因此没有任何问题。

2.若是此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,因为该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,因此当前 Activity 退出时它的内存并不会被回收,这就形成泄漏了。

固然,Application 的 context 不是万能的,因此也不能随便乱用,例如Dialog必须使用 Activity 的 Context。

2. 非静态内部类建立静态实例形成的内存泄漏

非静态内部类默认会持有外部类的引用,而该非静态内部类又建立了一个静态的实例,该实例的生命周期和应用的同样长,这就致使了该静态实例一直会持有该Activity的引用,致使Activity的内存资源不能正常回收。

3. 匿名内部类形成的内存泄漏

匿名内部类默认也会持有外部类的引用。

若是在Activity/Fragment中使用了匿名类,并被异步线程持有,若是没有任何措施这样必定会致使泄漏。

例子:Handler形成的内存泄漏。

修复方法:在 Activity 中避免使用非静态内部类或匿名内部类,好比将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。若是须要用到Activity,就经过弱引用的方式引入 Activity,避免直接将 Activity 做为 context 传进去。另外, Looper 线程的消息队列中仍是可能会有待处理的消息,因此咱们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。
代码示例如图:

4. 资源未关闭形成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File, Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,不然这些资源将不会被回收,形成内存泄漏。

5. 不良代码形成的内存使用压力

有些代码并不形成内存泄漏,可是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。好比,Adapter里没有复用convertView等。

3、使用Android Studio的Memory Monitor来分析内存泄漏状况

先看一下Android Studio 的 Memory Monitor界面:

最原始的内存泄漏排查方式以下:

重复屡次操做关键的可疑的路径,从内存监控工具中观察内存曲线,看是否存在不断上升的趋势,且退出一个界面后,程序内存迟迟不下降的话,可能就发生了严重的内存泄漏。

这种方式能够发现最基本,也是最明显的内存泄漏问题,对用户价值最大,操做难度小,性价比极高。

 

下面就开始用一个简单的例子来讲明一下如何排查内存泄漏。

首先,建立了一个TestActivity类,里面的测试代码以下:

@Override  
protected voidprocessBiz() {      
    mHandler = new Handler();     
    mHandler.postDelayed(newRunnable() {         
        @Override         
        public voidrun() {              
            MLog.d("------postDelayed------");         
        }      
    }, 800000L); 
}

运行项目,并执行如下操做:进入TestActivity,而后退出,再从新进入,如此操做几回后,最后最终退出TestActivity。这时发现,内存持续增高,如图所示:

这时咱们能够假设,这里可能出现了内存泄漏的状况。那么,如何继续定位到内存泄漏的地址呢?这时候就得点击“Dump java heap”按钮来收集具体的信息了。

下面咱们就要须要使用Android Studio生成Java Heap文件来分析内存状况了。

注意,在点击 Dump java heap 按钮以前,必定要先点击Initate GC按钮强制GC,建议点击后等待几秒后再次点击,尝试屡次,让GC更加充分。而后再点击Dump Java Heap按钮。

这时候会生成一个Java heap文件并在新的窗口打开:

这时候,点击右上角的“Analyzer Task”,再点击出现的绿色按钮,让Android studio帮咱们自动分析出有可能潜在的内存泄漏的地方:

如上图所示,Android studio提示有3个TestActivity对象可能出现了内存泄漏。并且左边的Reference Tree(引用树),也大概列出了该实体类被引用的路径。经过这些咱们就能大概能猜到是哪里致使了内存泄漏。

相关文章
相关标签/搜索