Android内存泄露OOM的原因及解决方法

  • 什么是内存溢出?

    OOM(out of memory)即内存溢出.在程序中,对内存使用超过一定的阀值就会导致内存溢出,而new出来的Object对象在使用完后触发GC也无法被回收 叫做内存泄漏

  • OOM的可能导致的现象?

    1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)

    2.由于APP运行内存限制,会导致直接崩溃(OutOfMemoryError)

    3.触发Low Memory Killer机制,应用莫名被杀

    Android内存进程管理,Low Memory Killer机制

  • 怎么知道内存泄漏呢?

内存泄漏发生时的主要表现为内存抖动,可用内存慢慢变少,在Android studio中可以通过Android Profiler工具查看内存抖动情况

内存溢出(泄漏)如何产生的?:

  • 一. 创建的资源没有及时释放:

如何避免:

1.资源性对象及时关闭,使用的任何资源都要及时关闭或者异常处理,保证在最恶劣的情况下资源可以得到释放
(如:Cursor、File、Receiver、Sensor)

2.资源的注册和反注册成对出现(广播,观察者)
如事件注册后未注销,会导致观察者列表中持有Context对象的引用

3.页面退出时及时清理一些资源占用(集合对象,WebView)**
容器中的对象在不用的时候及时清理,WebView存在着内存泄漏的问题,在应用中只要使用一次,WebView,内存就不会被释放掉.

4.资源重复利用(使用adapter的时候使用convertView)**
  • 二. 保存了耗用内存过大的对象(Bitmap)

如何避免:

1.Bitmap没有使用的时候及时recycle释放内存

2.对大图进行压缩,使用软引用或弱引用(使用这两种引用代码需要做不为空判断)

3.使用缓存技术(LruCache和DiskLruCache)
  • 三.Static引用的资源 消耗过多的实例(Context的使用)

如何避免:

1.尽量避免static成员变量引用资源 消耗过多的实例,比如Context;静态变量不要持有大数据对象

2.使用软引用代替强引用**

3.尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题

4.对于内部类尽量使用静态内部类,避免由于内部类导致的内存泄漏(静态内部类可以通过软引用使用外部的Context)如:Handler使用静态内部类
  • 四.线程生命周期不可控导致内存泄漏

如何避免:

1.将线程的内部类,改为静态内部类,在线程内部采用弱引用保存Context引用

2.线程优化:避免程序中存在大量的Thread.可以使用线程池,并且页面退出时,终止线程.**


例子: 在activity中handler发一个延时任务,activity退出后,延迟任务的message还在主线程,它持有activity的handler引用,所以造成内存泄漏(handler为非静态类,它会持有外部类的引用,也就是activity);  这里可以把handler声明为static的,则handler的存活和activity生命周期无关了,如果handler内部使用外部类的非static对象(如: Context),应该通过弱引用传入,activity销毁时,移除looper线程中的消息.

常见的内存泄漏案例分析

  • 1、单例模式引起的内存泄露

由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏

内存泄漏代码片段:

public class MyInstance {
    private static MyInstance mMyInstance;
    private Context mContext;

    private MyInstance(Context context) {
        this.mContext = context;
    }
    public static MyInstance getInstance(Context context) {
        if (mMyInstance == null) {
            synchronized (MyInstance.class) {
                if (mMyInstance == null) {
                    mMyInstance = new MyInstance(context);
                }
            }
        }
        return mMyInstance;
    }

    private View mView = null;
    public void setXXView(View xxView) {
        mView = xxView;
    }
}

解决方案:

  1. 传入的Context使用ApplicationContext;
  2. 将该属性的引用方式改为弱引用;
public class MyInstance {
    private static MyInstance mMyInstance;
    private Context mContext;

    private MyInstance(Context context) {
        this.mContext = context.getApplicationContext();
    }

    public static MyInstance getInstance(Context context) {
        if (mMyInstance == null) {
            synchronized (MyInstance.class) {
                if (mMyInstance == null) {
                    mMyInstance = new MyInstance(context);
                }
            }
        }
        return mMyInstance;
    }

    private WeakReference mView = null;
    public void setXXView(View xxView) {
        mView = new WeakReference(xxView);
    }
}
  • Handler引发的内存泄漏

内存泄漏代码片段:
当Activity退出时,延时任务Message还在主线程的MessageQueue中等待,此时的Message持有Handler的强引用,并且Handler是Activity类的非静态内部类,所以也默认持有Activity的强引用.

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 5000);
    }

解决方案: 使用静态内部类,通过弱引用传入外部类的Context

    private final Handler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 5000);
    }

    static class MyHandler extends Handler {
        private SoftReference reference;

        public MyHandler(Activity activity) {
            // 持有 Activity 的软引用
            reference = new SoftReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = reference.get();
            if (activity != null && !activity.isFinishing()) {
                switch (msg.what) {
                    // 处理消息
                }
            }
        }
    }
  • 内部类引起的内存泄漏

内部类默认持有外部类强引用,容易出现内存泄漏

内存泄漏代码片段:

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

    public void run() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    SystemClock.setCurrentTimeMillis(1000);
                }
            }
        }).start();
    }

解决方案: 使用static,变成静态匿名内部类

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

    public static void run() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    SystemClock.setCurrentTimeMillis(1000);
                }
            }
        }).start();
    }
  • Activity Context 的不正确使用引起的内存泄漏

使用ApplicationContext代替Activity Context ,因为ApplicationContext的生命周期也就是该应用生命周期,不依赖于activity的生命周期

  • 内存泄露的工具?
1. MAT工具(很全面,但入手较难,MAT为Eclipse自带工具)
2. Android Profiler(图像化工具,AndroidStudio自带工具)
3. LeakCanary工具(简便)
    源码:https://github.com/square/leakcanary
     支持Eclipse的库:http://download.csdn.net/detail/ytuglt/9533490

MAT的使用:

一丶内存泄露简单分析:

Android内存泄露可能会出现的现象:

1.频繁打开应用会出现界面卡顿,动画不流畅;

2.操作中,Logcat频繁输出GC日志;

例如:
        dalvikvm        GC_FOR_ALLOC  freed  9534k, 16%  free  72342k/88240k, paused  43ms,  totol 29ms
        dalvikvm        GC_FOR_ALLOC  freed  9534k, 16%  free  72342k/88240k, paused  43ms,  totol 39ms
        dalvikvm        GC_FOR_ALLOC  freed  9534k, 16%  free  72342k/88240k, paused  43ms,  totol 39ms

二丶内存泄露深入分析

MAT工具关键字段:
    Dominator Tree: 可以列出所有对象,以及它们占用的内存大小
    Shallow Heap:对象本身的内存大小;
    Retained Heap:对象本身以及它持有的所有对象的内存总和;
    SystemClass:是指系统管理的对象;
    Histogram:列出内存中所有的类,以及内存的实例的个数;(object:内存中实例的个数);

原则:如果一个对象直接或者间接的被GC Roots引用,是不会被系统回收掉,因此我们可以查看xxxx(Bitmap)对象到GC Roots的引用路径来分析问题;

1.左下角有个小圆点,代表着这个对象被GC Roots直接引用;

Android内存泄露OOM的原因及解决方法_第1张图片

2.引用流程:

GC Roots 持有着我们创建出来的Thread对象,Thread对象引用了activity的实例,activity的实例引用了我们的bitmap对象,因此当activity退出,Thread仍保持着activity的引用,导致它引用的内存不能被回收. 三丶解决方案 1.将thread移到后台服务,使thread与activity接触依赖 2.activity退出时,停止thread,让thread和activity的生命周期保持一致

常见内存泄露代码: new Thread(){}是一个匿名内部类,会隐式的持有一个外部类的对象MainActivity; 如果MainActivity在Thread执行前就销毁了,那么这个activity实例就发生了内存泄露; 根本原因:长生命周期对象(thread)持有短生命周期对象(activity)的引用,导致内存泄露;

Android内存泄露OOM的原因及解决方法_第2张图片

内存泄漏检测:

在执行某种操作后进行一次GC,内存没有明显的回落。此时即可以断定代码中可能存在内存泄漏


相关链接直达:

Android APP性能优化之 ---- 布局优化(一)

Android APP性能优化之 ---- 内存优化(二)

Android APP性能优化之 ---- 代码优化(三)

Android APP性能优化之 ---- 优化监测工具(四)

Android APP性能优化之 ---- APK瘦身 App启动优化

Android内存泄露OOM的原因及解决方法

你可能感兴趣的:(Android,#,性能优化)