内存泄露问题

http://blog.csdn.net/guolin_blog/article/details/42238633java

问题说明ide

    因为Android是为移动设备开发的操做系统,咱们在开发应用程序的时候应当始终把内存问题充分考虑在内。虽然Android系统拥有垃圾自动回收机制,但这并不意味着咱们就能够彻底忽略什么时候去分配或释放内存。即便咱们在写程序的时候,会去注意这个问题,仍是会颇有可能出现内存泄露或其它类型的内存问题。因此,惟一可以解决问题的办法,就是尝试去分析应用程序的内存使用状况。工具

答题技巧学习

    虽然说如今的手机内存都已经很是大了,可是咱们你们都知道,系统是不可能将全部的内存都分配给咱们的应用程序的。没错,每一个程序都会有可以使用的内存上限,这被称为堆大小(Heap Size)。不一样的手机,堆大小也不尽相同,随着如今硬件设备不断提升,堆大小也已经由Nexus One时的32MB,变成了Nexus 5时的192MB。若是你们想要知道本身手机的堆大小是多少,能够调用以下代码:操作系统

[java] view plaincopyprint?.net

 ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);插件

 int heapSize = manager.getMemoryClass();线程

ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();

结果是以MB为单位进行返回的,咱们在开发应用程序时所使用的内存不能超出这个限制,不然就会出现OutOfMemoryError。日志

若是咱们想要更加清楚地实时知晓当前应用程序的内存使用状况,咱们须要经过DDMS中提供的工具来实现。打开DDMS界面,在左侧面板中选择你要观察的应用程序进程,而后点击Update Heap按钮,接着在右侧面板中点击Heap标签,以后不停地点击Cause GC按钮来实时地观察应用程序内存的使用状况便可,以下图所示:orm

 

接着继续操做咱们的应用程序,而后继续点击Cause GC按钮,若是你发现反复操做某一功能会致使应用程序内存持续增高而不会降低的话,那么就说明这里颇有可能发生内存泄漏了。

你们须要知道的是,Android中的垃圾回收机制并不能防止内存泄漏的出现,致使内存泄漏最主要的缘由就是某些长存对象持有了一些其它应该被回收的对象的引用,致使垃圾回收器没法去回收掉这些对象,那也就出现内存泄漏了。好比说像Activity这样的系统组件,它又会包含不少的控件甚至是图片,若是它没法被垃圾回收器回收掉的话,那就算是比较严重的内存泄漏状况了。

    下面咱们来模拟一种Activity内存泄漏的场景,内部类相信你们都有用过,若是咱们在一个类中又定义了一个非静态的内部类,那么这个内部类就会持有外部类的引用,以下所示:

[java] view plaincopyprint?

public class MainActivity extends ActionBarActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

LeakClass leakClass = new LeakClass();

}

 

class LeakClass {

}

......

}

public class MainActivity extends ActionBarActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakClass leakClass = new LeakClass();
    }
 
    class LeakClass {
 
    }
    ......
}

目前来看,代码仍是没有问题的,由于虽然LeakClass这个内部类持有MainActivity的引用,可是只要它的存活时间不会长于MainActivity,就不会阻止MainActivity被垃圾回收器回收。那么如今咱们来将代码进行以下修改:

[java] view plaincopyprint?

public class MainActivity extends ActionBarActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

LeakClass leakClass = new LeakClass();

leakClass.start();

}

class LeakClass extends Thread {

@Override

public void run() {

while (true) {

try {

Thread.sleep(60 * 60 * 1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

......

}

public class MainActivity extends ActionBarActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakClass leakClass = new LeakClass();
        leakClass.start();
    }
 
    class LeakClass extends Thread {
 
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(60 * 60 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    ......
}

这下就有点不太同样了,咱们让LeakClass继承自Thread,而且重写了run()方法,而后在MainActivity的onCreate()方法中去启动LeakClass这个线程。而LeakClass的run()方法中运行了一个死循环,也就是说这个线程永远都不会执行结束,那么LeakClass这个对象就一直不能获得释放,而且它持有的MainActivity也将没法获得释放,那么内存泄露就出现了。

如今咱们能够将程序运行起来,而后不断地旋转手机让程序在横屏和竖屏之间切换,由于每切换一次Activity都会经历一个从新建立的过程,而前面建立的Activity又没法获得回收,那么长时间操做下咱们的应用程序所占用的内存就会愈来愈高,最终出现OutOfMemoryError。

下面我贴出一张不断切换横竖屏时GC日志打印的结果图,以下所示:

 

能够看到,应用程序所占用的内存是在不断上升的。最可怕的是,这些内存一旦升上去了就永远不会再降下来,直到程序崩溃为止,由于这部分泄露的内存一直都没法被垃圾回收器回收掉。

    那么经过上面学习DDMS工具这种方式,如今咱们已经能够比较轻松地发现应用程序中是否存在内存泄露的现象了。可是若是真的出现了内存泄露,咱们应该怎么定位到具体是哪里出的问题呢?这就须要借助一个内存分析工具了,叫作Memory Analyzer Tool(MAT)。这个工具分为Eclipse插件版和独立版两种,若是你是使用Eclipse开发的,那么可使用插件版MAT,很是方便。若是你是使用Android Studio开发的,那么就只能使用独立版的MAT了。

那么接下来咱们开始学习如何去分析内存泄露的缘由,首先仍是进入到DDMS界面,而后在左侧面板选中咱们要观察的应用程序进程,接着点击Dump HPROF file按钮,以下图所示:

 

点击这个按钮以后须要等待一段时间,而后会生成一个HPROF文件,这个文件记录着咱们应用程序内部的全部数据。可是目前MAT仍是没法打开这个文件的,咱们还须要将这个HPROF文件从Dalvik格式转换成J2SE格式,使用hprof-conv命令就能够完成转换工做,以下所示:

[plain] view plaincopyprint?

hprof-conv dump.hprof converted-dump.hprof  //直接进入hprof-conv坐在目录执行该命令

hprof-conv命令文件存放于<Android Sdk>/platform-tools目录下面。另外若是你是使用的插件版的MAT,也能够直接在Eclipse中打开生成的HPROF文件,不用通过格式转换这一步。

好的,接下来咱们就能够来尝试使用MAT工具去分析内存泄漏的缘由了,这里须要提醒你们的是,MAT并不会准确地告诉咱们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,咱们须要本身去分析这些数据来去判断究竟是不是真的发生了内存泄漏。那么如今运行MAT工具,而后选择打开转换事后的converted-dump.hprof文件,以下图所示:

 

MAT中提供了很是多的功能,这里咱们只要学习几个最经常使用的就能够了。上图最中央的那个饼状图展现了最大的几个对象所占内存的比例,这张图中提供的内容并很少,咱们能够忽略它。在这个饼状图的下方就有几个很是有用的工具了,咱们来学习一下。

Histogram能够列出内存中每一个对象的名字、数量以及大小。

Dominator Tree会将全部内存中的对象按大小进行排序,而且咱们能够分析对象之间的引用结构。

通常最经常使用的就是以上两个功能了,那么咱们先从Dominator Tree开始学起。如今点击Dominator Tree,结果以下图所示:

 

这张图包含的信息很是多,我来带着你们一块儿解析一下。首先Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,所以从上图中看,前两行的Retained Heap是最大的,咱们分析内存泄漏时,内存最大的对象也是最应该去怀疑的。

另外你们应该能够注意到,在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是能够被GC Roots访问到的,根据上面的讲解,能够被GC Root访问到的对象都是没法被回收的。那么这就说明全部带红色的对象都是泄漏的对象吗?固然不是,由于有些对象系统须要一直使用,原本就不该该被回收。咱们能够注意到,上图当中全部带红点的对象最右边都有写一个System Class,说明这是一个由系统管理的对象,并非由咱们本身建立并致使内存泄漏的对象。

那么上图中就没法看出内存泄漏的缘由了吗?确实,内存泄漏原本就不是这么容易找出的,咱们还须要进一步进行分析。上图当中,除了带有System Class的行以外,最大的就是第二行的Bitmap对象了,虽然Bitmap对象如今不能被GC Roots访问到,但不表明着Bitmap所持有的其它引用也不会被GC Roots访问到。如今咱们能够对着第二行点击右键 -> Path to GC Roots -> exclude weak references,为何选择exclude weak references呢?由于弱引用是不会阻止对象被垃圾回收器回收的,因此咱们这里直接把它排除掉,结果以下图所示:

 

能够看到,Bitmap对象通过层层引用以后,到了MainActivity$LeakClass这个对象,而后在图标的左下角有个红色的图标,就说明在这里能够被GC Roots访问到了,而且这是由咱们本身建立的Thread,并非System Class了,那么因为MainActivity$LeakClass能被GC Roots访问到致使不能被回收,致使它所持有的其它引用也没法被回收了,包括MainActivity,也包括MainActivity中所包含的图片。

经过这种方式,咱们就成功地将内存泄漏的缘由找出来了。这是Dominator Tree中比较经常使用的一种分析方式,即搜索大内存对象通向GC Roots的路径,由于内存占用越高的对象越值得怀疑。

接下来咱们再来学习一下Histogram的用法,回到Overview界面,点击Histogram,结果以下图所示:

 

这里是把当前应用程序中全部的对象的名字、数量和大小所有都列出来了,须要注意的是,这里的对象都是只有Shallow Heap而没有Retained Heap的,那么Shallow Heap又是什么意思呢?就是当前对象本身所占内存的大小,不包含引用关系的,好比说上图当中,byte[]对象的Shallow Heap最高,说明咱们应用程序中用了不少byte[]类型的数据,好比说图片。能够经过右键 -> List objects -> with incoming references来查看具体是谁在使用这些byte[]。

那么经过Histogram又怎么去分析内存泄漏的缘由呢?固然其实也能够用和Dominator Tree中比较类似的方式,即分析大内存的对象,好比上图中byte[]对象内存占用很高,咱们经过分析byte[],最终也是能找到内存泄漏所在的。

    好了,这大概就是MAT工具最经常使用的一些用法了,固然这里还要提醒你们一句,工具是死的,人是活的,MAT也没有办法保证必定能够将内存泄漏的缘由找出来,仍是须要咱们对程序的代码有足够多的了解,知道有哪些对象是存活的,以及它们存活的缘由,而后再结合MAT给出的数据来进行具体的分析,这样才有可能把一些隐藏得很深的问题缘由给找出来。

相关文章
相关标签/搜索