Android内存泄露与内存溢出

来源:互联网 发布:数据安全 国家安全法 编辑:程序博客网 时间:2024/05/22 17:01

一、 内存泄漏与内存溢出(OOM)

1. 内存泄露

  • 垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。
  • 内存泄露的危害: (1)过多的内存泄露最终会导致内存溢出(OOM)(2)内存泄露导致可用内存不足,会触发频繁GC,不管是Android2.2以前的单线程GC还是现在的CMS和G1,都有一部分的操作会导致用户线程停止(就是所谓的Stop the world),从而导致UI卡顿。

2. 内存溢出(OOM)

  • Android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。
  • ActivityManager.getMemoryClass()可以查询当前APP的Heap Size阈值,单位是MB。
  • 在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。
  • Android 2.x系统,当dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM,也就是说在2.x系统中,考虑native heap对每个进程的内存限制。
  • Android 4.x系统,废除了native的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= dalvik heap 最大值的时候就会发生OOM(art运行环境的统计规则还是和dalvik保持一致),也就是说在4.x系统中,不考虑native heap对每个进程的内存限制,native heap只会收到本机总内存(包括RAM以及SWAP区或分页文件)的限制。

二、 LeakCanary内存检测工具配置

LeakCanary在GitHub上地址:https://github.com/square/leakcanary

在app的build.gradle中加入:

dependencies {    //leakcanary内存检测三方库    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'}

在项目的Application中加入(没有则创建):

    @Override    public void onCreate() {        super.onCreate();        //初始化内存检测工具        if (LeakCanary.isInAnalyzerProcess(this)) {            // This process is dedicated to LeakCanary for heap analysis.            // You should not init your app in this process.            return;        }        LeakCanary.install(this);        // Normal app init code...    }}

三、 内存泄漏检测案例

非静态内部类引起的内存泄漏

导致内存泄漏的错误代码:

public class LeakActivity extends AppCompatActivity {    private static User mUser;        ……    /**     * 非静态内部类引起的内存泄漏     */    private void innerClassLeak() {        mUser = new User();    }    class User {        private String userName;    }}

内存泄漏检测工具界面截图:
图片 1

分析结果:
从下往可以看出导致内存泄漏是LeakActivity中private static User mUser;导致。导致该Activity销毁时释放不了.

解决后代码:

 private static User mUser;        ……    /**     * 非静态内部类引起的内存泄漏     */    private void innerClassLeak() {        mUser = new User();    }    static  class User {        private String userName;    }}

将非静态类加入static修饰变为静态类,或者去除 private static User mUser;static修饰符

Thread引起的内存泄漏

导致内存泄漏的错误代码:

/** * 线程引起内存泄漏 */private void threadLeak() {    new Thread(new Runnable() {        @Override        public void run() {            //耗时操作            SystemClock.sleep(10000);//睡眠10s,模拟耗时操作        }    }).start();}

内存泄漏检测工具界面截图:
图片 2

分析结果:
可以看出内存泄漏的原因是LeakActivity中有个任务导致,当次Activity销毁时,此线程任务未完成,导致此Activity无法释放。

解决后代码:

/** * 线程引起内存泄漏 */private void threadLeak() {    new MyThread().start();}static class MyThread extends Thread{    @Override    public void run() {        //耗时操作        SystemClock.sleep(10000);//睡眠10s,模拟耗时操作        super.run();    }}

自定义线程池工具类来开启此线程也可以。

Handler引起的内存泄漏

导致内存泄漏的错误代码:

Handler handler= new Handler();/** * Handler导致内存泄漏 */private void HandlerLeak() {    handler.postDelayed(new Runnable() {        @Override        public void run() {            handler.postDelayed(this,1000);//每隔1000毫秒,循环发送消息        }    },1000);}

内存泄漏检测工具界面截图:
图片 3

分析结果:
从下到上可以看出LeakActivity中消息任务导致内存泄漏,此Activity关闭时消息任务还未关闭,导致此Activity未能销毁。

解决后代码:

Handler handler= new Handler();/** * Handler导致内存泄漏 */private void HandlerLeak() {    handler.postDelayed(new Runnable() {        @Override        public void run() {            handler.postDelayed(this,1000);//每隔1000毫秒,循环发送消息        }    },1000);}@Overrideprotected void onDestroy() {    super.onDestroy();    handler.removeCallbacksAndMessages(null);//remove正在执行的消息任务}

当Activity销毁时调用关闭所有消息任务

单例Toast引起内存溢出

导致内存溢出的错误代码:

/** * 静态Toast引起的内存泄漏 */private void ToastLeak() {    ToastUtils.showToast(this,new Date().getTime()+"");}private static Toast toast;public static  void showToast(Context context, String msg){    if(toast==null)    toast = Toast.makeText(context,"",Toast.LENGTH_SHORT);    toast.setText(msg);    toast.show();}

内存泄漏检测工具界面截图:
图片 4

分析结果:
从下至上看出LeakActivity 中Toast(这里使用了静态Toast) 的Context导致LeakActivity无法销毁,内存泄漏

解决后代码:

/** * 静态Toast引起的内存泄漏 */private void ToastLeak() {    ToastUtils.showToast(this,new Date().getTime()+"");}private static Toast toast;public static  void showToast(Context context, String msg){    if(toast==null)    toast = Toast.makeText(context.getApplicationContext(),"",Toast.LENGTH_SHORT);    toast.setText(msg);    toast.show();}

这里使用的是应用全局的Context。
什么是Context:https://possiblemobile.com/2013/06/context/

四、如何避免内存泄漏

  • 使用轻量的数据结构:使用ArrayMap/SparseArray来代替HashMap,ArrayMap/SparseArray是专门为移动设备设计的高效的数据结构。
  • 不要轻易使用Enum。
  • Bitmap压缩处理:使用知名的第三方开源库处理。
  • 不要使用String进行字符串拼接:频繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和内存抖动。
  • 谨慎使用static对象:static对象的生命周期过长,应该谨慎使用。
  • Context持有导致内存泄漏:Activity中的context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
1 0
原创粉丝点击