Android内存泄漏分析与总结

为了书写方便内存泄漏的实例,本文用leakcanary检测内存泄漏。

Java中的内存分配

在了解内存泄漏之前,先简单了解一下java的内存分配,主要分为一下三块:

  • 静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量。
  • 栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存。
  • 堆区:通常存放new出来的对象,由GC负责回收

Java中四种引用类型

强引用

在日常的编程中,用的最多的强引用如下:

User user = new User();

对于强引用的回收,JVM是不会让GC去主动回收具有强引用的对象,而我们需要回收强引用对象的时候,常常通过置空,即object=null。这样GC就会去回收该强引用对象

软引用

软引用的创建

SoftReference<String> softRef = new SoftReference<String>();

当一个对象只具有软引用的时候,内存空间足够的话,GC不会回收,而当内存空间不足的时候,GC就会回收这些对象。在Android2.3之后,GC会很频繁的操作,因此在用软引用的时候要注意选择不常用的对象作为软引用,否则会降低效率。

弱引用

弱引用的创建

WeakReference<String> softRef = new WeakReference<String>();

当一个对象只具有软引用的时候,内存空间足够的话,GC发现即回收。当然,由于GC的线程优先级很低,因此弱引用对象不一定会被及时的回收。

虚引用

虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。

内存泄漏定义

内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏(摘自百度百科)。在android里面引起内存泄漏指的是对象的生命周期结束,而该对象依然被其他对象所持有,导致该对象所占内存无法释放。

内存泄漏带来的影响

在android里面,出现内存泄漏会导致系统为应用分配的内存会不断减少,从而造成app在运行时会出现卡断(内存占用高时JVM虚拟机会频繁触发GC),影响用户体验。同时,可能会引起OOM(内存溢出),从而导致应用程序崩溃!

Android中常见导致内存泄漏操作

传统的内存泄漏是由于分配的内存未能及时释放导致的。当一个对象不再被使用时,该对象仍然存在强引用的话(User user=new User(),其他对象引用这个user实例的时候就是一个强引用),垃圾回收器就无法对其进行垃圾回收,便会造成内存泄漏。在android里面,日常所见的泄漏基本出现在context的引用上,尤其activity对象,它会引用大量占用内存的对象,服务,资源,view等。下面举例说明八种常见的可能导致activity泄漏的情况。

集合类泄漏

如果某个集合是全局性的变量(比如 static 修饰),集合内直接存放一些占用大量内存的对象(而不是通过弱引用存放),那么随着集合 size 的增大,会导致内存占用不断上升,而在 Activity 等销毁时,集合中的这些对象无法被回收,导致内存泄露。比如我们喜欢通过静态 HashMap 做一些缓存之类的事,这种情况要小心,集合内对象建议采用弱引用的方式存取,并考虑在不需要的时候手动释放。

单例模式(静态activity的引用)

单例模式造成的内存泄漏可能在你的项目中存在,只是我们平常没怎么关注,并不是所有的内存泄漏都会引起卡顿或者闪退,造成内存泄漏的写法如下

public class RequestImpl {

    private Context context;
    private static RequestImpl mInstance;

    public static RequestImpl getInstance(Context context) {

        if (mInstance == null) {
            mInstance = new RequestImpl(context);
        }
        return mInstance;
    }

    private RequestImpl(Context context) {
        this.context = context;
    }
}

当我们在调用getInstance的时候不经意的传入了一个activity的this,而不是传入appication的context时候就会造成内存泄漏如下

这里写图片描述

解决方法就是在调用getInstance的时候传入application的context,或者在RequestImpl的构造函数里面调用context.getApplicationContext().

非静态内部类

非静态内部类造成的内存溢出常出Handler,Thread,Timer Task(定时任务),这些耗时操作如果在activity生命周期结束后还在运行的话,可能会造成内存溢出。如下Thread写法

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testInnner();
    }

    private void testInnner() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

内存泄漏如下,内部类Thread引用了Mainctivity对象

这里写图片描述

下面再列举一个非常常见的handler可能造成的内存泄漏

这里写图片描述

通过字面翻译android系统给出的提示这个handler类应该是static的,否则可能引起内存泄漏,而ctrl+F1查看到的解决建议如下
since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
翻译下来的意思就是,内部类声明在外部类从而阻止了GC的回收,如果该handler实例的轮询器或者消息队列是在非主线程创建的,那这样创建handler实例是不存在什么问题,如果looper或者messagequeue创建在主线程,我们需要将handler声明为静态内部类,同时用WeakRefenrce与引用外部对象(弱引用)。因此有如下的handler解决方案

    MyHandler mHandler = new MyHandler(this);

    static class MyHandler extends Handler {
        WeakReference<MainActivity> activityReference;

        MyHandler(MainActivity activity) {
            activityReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityReference.get();
            //判断是因为GC是对弱引用进行回收的
            if (activity != null) {
                activity.getTextView().setText("测试");
            }
        }
    }

对于以上现象出现,将testInner方法改为静态方法即可。类似的TimerTask定时任务也会出现此种情况.当书写内部类的时候要确保自己的耗时操作在activity结束后没有引用activity对象

getSystemServie
 AudioManager   mAudioManager= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

SensorManager mSensorManager =(SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

类似以上context.getSystemService()是创建了一个服务,在activity生命周期结束后,一定要及时释放。调用unregisterListener方法。

资源未关闭造成的内存泄漏

BroadcastReceiver,ContentObserver 之类的没有解除注册啊;Cursor,Stream 之类的没有 close 啊;无限循环的动画在 Activity 退出前没有停止啊;一些其他的该 释放或者回收的没有被操作,比如自定义view的属性获取类 TypedArray需要调用recycle

内存泄漏的检测与分析

因为项目中是在用Android Studio进行开发,且最近升级到了2.2的版本,因此主要利用Android Studio来进行分析。而且AS目前已经完善了这些应用的性能分析功能。个人认为没必要再去用Eclipse的MAT插件来分析。

利用内存泄漏检测开源项目LeakCanary

注意leakcanary要应用的文件读写权限,因此注意适配android6.0的权限动态申请。 leakcanary的使用相当的方便,先通过gradle配置leakcanary库。然后在应用程序的Application中调用如下方法即可:

LeakCanary.install(this);

leak信息

从上往下看就可以看到具体是哪部分对象的引用造成的内存泄漏。
同时,我们可以将产生的.hropf文件用Android Studio打开(直接拖拽进AS即可)。

ASin

在左上角划横线的地方选择树结构,可以很快的定位到应用程序的包,然后选中包(如果你想单独定位某一个类,就选中该类),再展开Anlyzer Tasks,然后点击右上角的绿色三角箭头,启动分析。

task的默认选项

这两个选项都是默认选择,查找存在内存泄漏的activity以及定义的字符串存在重复定义的地方。点击run后,运行结果如下图所示:

runresult

该图右边框显示的是引起泄漏的activity以及重复定义的string,下方方框列出了详细的对象引用。而引起内存泄漏的引用标注为蓝色或者红色(红色为leakcanary)。

利用Android Studio自带的Android Monitor

Android Monitor就在logcat的旁边,当然通过tools里面也可以打开Android Monitor.当app运行在手机上的时候,monitors如下图

这里写图片描述

点击小方框的 dump java heap(java堆转储),as会自动生成.hprof文件,并自动打开。打开后,具体操作和上面的LeakCanary产生的.hprof文件的分析方法一致。

总结

内存泄漏的检测与分析是要伴随着整个app的开发流程,一步一个脚印,才能开发出优秀的app。也欢迎大家一起讨论。欢迎加讨论群205099369。同时,这边博客参阅以及借鉴了
Eight Ways Your Android App Can Leak Memory
Android内存泄漏总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值