美文网首页
内存泄漏的检测与解决方法

内存泄漏的检测与解决方法

作者: Time_x | 来源:发表于2019-05-12 16:11 被阅读0次

    实训通关第三期

    1.如何检测内存问题

    内存泄漏:在基于Java的运行中,内存泄漏是一种编程错误,它会导致应用程序对已经不需要再使用对象的引用。所以,无法回收该系统给该对象分配的内存。最终导致OOM(OutOfMemoryError 内存泄漏) 崩溃。

    简单来说就是:一些对象有着有限的生命周期。当这些对象所要做的事情完成了,我们希望他们会被回收掉。但是如果有一系列对这个对象的引用,那么在我们期待这个对象生命周期结束的时候被收回的时候,它是不会被回收的。它还会占用内存,这就造成了内存泄露。持续累加,内存很快被耗尽。

    通过工具来检测是否错在内存泄漏的问题:

    1. 通过Android Studio 自带的工具 Android Profilter 找到MEMORY这个栏目 进行检测

    2.LeakCanary

    第一步使用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'

    compile'com.android.support:multidex:1.0.1'

    第二步在我们自定义继承Application类的onCreate()方法里面

    //检测内存泄漏

    if (LeakCanary.isInAnalyzerProcess(this)) {

    return;

    }

    LeakCanary.install(this);

    如果需要具体的检查内存泄漏的时候:

    //修复内存泄漏

    public static RefWatcher getRefWatcher(Context context) {

    MyApp applicationContext = (MyApp) context.getApplicationContext();

    return applicationContext.refWatcher;

    }

    完成以上操作之后会在我们的手机或者模拟器上生成此应用

    LeakCanary会找到并修复 多个内存泄漏问题 将OOM崩溃的几率降低94%。

    详解:

    https://blog.csdn.net/qq_20280683/article/details/77964208

    https://www.cnblogs.com/fuyaozhishang/p/7753013.html

    2.内存溢出和泄漏的区别及常见内存问题

    1.内存泄漏 memory leak

    指程序在申请内存后,被某个对象一直持有,无法释放已申请的内存空间 一次内存泄漏危害可以忽略,但是内存泄漏堆积后果是很严重的。无论你有多少内存,迟早被占光。

    内存泄漏又分好几种情况:内存泄漏的分类:以发生的方式来分类,内存泄漏可

    以分为 4 类:

    1. 常发性内存泄漏。

    发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄

    漏。

    2. 偶发性内存泄漏。

    发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发

    性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境

    和测试方法对检测内存泄漏至关重要。

    3. 一次性内存泄漏。

    发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块

    仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没

    有释放该内存,所以内存泄漏只会发生一次。

    4. 隐式内存泄漏。

    程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说

    这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个

    服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终

    耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏

    内存泄漏出现的一些实例:

    1.单例--生命周期

    分析:因为单例的生命周期和应用程序一样,如果单例对象持有了某个不再需要

    的对象的引用,(比如 Activity 的 context),那么这个 Activty 在单例没有被

    释放前将不会被释放。

    解决:我们可以让单例的引用为 Application 的 context

    2.Handler

    我们经常会在 activity 中这样使用 handler:

    class MyHandler extends Handler{

    ...

    }//使用

    MyHandler mHandler=new MyHandler(this);

    分析:由于 myHandler 是 Handler 的非静态匿名内部类的实例,所以它持有外部

    类 Activity 的引用,Looper 线程不断轮询处理消息,Activity 退出时如果消息队列

    里还有未处理的消息,消息队列的 Message 持有 mHandler 的引用,mHandler 又

    持有 Activity 的引用,所以导致 Activity 无法及时被 GC 回收。从而造成内存泄漏

    解决方法:

    1.创建静态 Handler 的匿名内部类 static class MyHandler extends Handler

    2.把对 Handler 持有的对象的使用弱引用 WeakReference context;

    3. 在 Activity 销 毁 时 移 除 消 息 队 列 中 的 任 务 或 消

    息 handler.removeCallbacksAndMessages(null);取消所有的消息的处理

    3.非静态内部类创建静态实例

    分析:非静态内部类可以自由使用外部类的所有变量和方法,非静态内部类,它

    默认持有外部类的引用,此时如果在外部类创建静态 static 的内部类的实例,或

    是声明为 static 静态成员变量,这样就导致内部类的生命周期和应用程序一样长,

    导致 Activity 无法正常销毁。

    解决方法:将非静态内部类转为静态内部类,这样就不会隐式持有外部类。

    4.线程造成的内存泄漏

    分析:我们常用的异步任务(如:AsyncTask)和 Runnable 都是匿名内部类,所以它

    们对当前的 Activity 都有一个隐式引用,若 Activity 销毁,但是线程的任务还没有

    完成,就会造成 Activity 的 gc 无法回收。

    解决方法:

    1.使用静态内部类 将 Runnable 内部类、AsyncTask 内部类声明为静态。

    2.销毁时取消相应的任务。

    5.资源未关闭

    BroadcastReceiver、File、Cursor、Stream、Bitmap 及时关闭和注销、否则不会

    被回收造成内存泄漏。

    6.系统服务、监听器未注销/移除

    有一些系统服务或监听器在不需要使用的时候再及时移除或注销

    7.动画

    对于有一些属性动画,属性为无限循环,这时候我们可以在 onStop 中停止动画。

    2.内存溢出 out of memory

    指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory;

    比如申请了一个 integer,但给它存了 long 才能存下的数,那就是内存溢出。

    内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是

    产生溢出。

    内存溢出出现的情况:

    1.对象内存过大(图片、Bitmap、XML)造成内存超出

    2.布局重复加载(比如列表控件 adapter 中没有复用 view 等)、界面横竖屏切换。

    应用资源过多,来不及加载。

    3.还有我们上面介绍的内存泄漏,过多的内存泄漏,也会导致虚拟机可分配的内

    存越来越少,这样也是容易出现 OOM。

    关于避免 OOM:

    A:减少 OOM 最重要的就是要尽量减少新分配出来的对象占用内存的大小,尽

    量使用更加轻量的对象。

    避免内存泄漏,见上面我们总结的一些情况(比如:善用 static、避免无关引

    用无法释放、善用 SoftReference/WeakReference/LruCache、谨慎 handler、线程

    等、及时关闭无用服务、监听。)

    如果代码中有大量字符串拼接操作,使用 StringBuilder 代替"+"。

    Bitmap 的不当处理极可能造成 OOM,其实很多 OOM 的原因都来源于此,所以

    一定要十分重视对 Bitmap 的优化。

    B:一直说 OOM 的出现是因为应用占用的内存(主要是指的 heap)超出了系统

    给我们分配内存的最大值,那有没有可能增加系统为我们的 App 分配的内存大

    小。--->使用 largeHeap,会请求系统为 Dalvik 虚拟机分配更大的内存空间。使用

    起来也很方便,只需在 manifest 文件 application 节点加入 android:largeHeap= “ true ” 即 可 。 作 为 验 证 , 可 以 通 过 打 印 两 者 的 值 。

    ActivityManager.getMemoryClass() 获 得 应 用 正 常 情 况 下 内 存 的 大 小 ,

    ActivityManager.getLargeMemoryClass()可以获得使用 largeHeap 最大的内存大小。

    但是这个东西需要慎用,不建议使用

    3.内存优化的方案

    1.对象引用。强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合

    理使用不同,选择不同的引用类型。

    2. 减少不必要的内存开销。注意自动装箱,增加内存复用,比如有效利用系统自

    带的资源、视图复用、对象池、Bitmap 对象的复用。

    3. 使用最优的数据类型。比如针对数据类容器结构,可以使用 ArrayMap 数据结

    构,避免使用枚举类型,使用缓存 Lrucache 等等。

    4. 图片内存优化。可以设置位图规格,根据采样因子做压缩,用一些图片缓存方

    式对图片进行管理等等

    启动模式的原理以及应用场景

    大家都知道启动模式是如何的应用的:

    第一种方式:

    在清单文件中直接注册:

    第二种方式:

    通过Intent.setFlags(int flags)设置启动模式:

    standard 标准模式 :

    每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否以及存在,此模式的Activity默认会进入启动它的Activity所属的任务栈中。

    应用场景:默认的应用场景

    singleTop 栈顶复用模式:

    如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时会回调onNewIntent方法,如果新Activity实例已经存在但不在栈顶,那么Activity依然会被重新创建;

    应用场景: 登录页面、WXPayEntryActivity、WXEntryActivity 、推送通知栏

    singleTask栈内复用模式

    只要Activity在一个任务栈中存在,那么多次启动此Activity都不会重新创建实例,并回调onNewIntent方法,此模式启动Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好A的实例放到栈中;

    应用场景:程序模块逻辑入口:主页面(Fragment的containerActivity)、WebView页面、扫一扫页面、电商中:购物界面,确认订单界面,付款界面

    singleInstance单实例模式

    如果哪一个activity设置了这种启动模式,那么只要一启动,就会把这个activity的实例放到一个独立的栈中,里面有且只有它自己一个实例, 以后如果还启动这个activity,将不会创建新的实例,而是把它所在的栈移动到最前面显示给用户看

    应用场景:系统Launcher、锁屏键、来电显示等系统应用

    相关文章

      网友评论

          本文标题:内存泄漏的检测与解决方法

          本文链接:https://www.haomeiwen.com/subject/gqbcaqtx.html