Android内存泄漏分析和解决方案

发生内存泄漏的原因
内存空间使用完毕后没有被回收,就会导致内存泄漏。虽然Java有垃圾回收机制,但是Java中任然存在很多造成内存泄漏的代码逻辑,垃圾回收器会回收掉大部分的内存空间,但是有一些内存空间还保持着引用,但是在逻辑上已经不会再用到的对象,这时候垃圾回收器就很无能为力,不能回收它们。
比如:

忘记释放分配的内存;
应用不需要这个对象了,但是却没有释放这个对象的引用;
强引用持有的对象,垃圾回收器是无法回收这个对象;
持有对象生命周期过长,导致无法回收;

Java判断无效对象的原理

图中的每个圆点都代表对象的内存资源,箭头代表可达路径。当圆点与GC Roots(Garbage Collection Roots)存在可达路径时,就表示当前资源正在被引用,虚拟机是无法对其进行回收的(如图中的黄色圆点),反过来,如果圆点与GC Roots不存在可达路径,则说明这个对象的内存空间不被引用,虚拟机可以将其回收掉。

Android(Java)平台的内存泄漏是指没用的对象资源与GC Roots之间保持可达路径,导致系统无法进行回收。
`

内存泄漏带来的危害
用户对单次的内存泄漏并没有什么感知,但是当泄漏积累到内存都被消耗完,就会导致卡顿,甚至崩溃。
内存泄漏是内存溢出OOM的重要原因之一,会导致Crash。
常见的可能发生内存泄漏的地方
1、最容易引发内存泄漏的问题是Context
例如Activity的Context,它包含大量的内存引用,一旦泄漏了Context,也就意味着泄漏它指向的所有对象。

造成Activity泄漏的常见原因:
Static Activities
在类中定义了static Activity变量,把正在运行的Activity实例赋值给这个静态变量。
当这个Activity生命周期结束后,但是这个静态变量却没有清空,就会导致内存泄漏。
因为static变量是贯穿这个应用的生命周期的,所以被泄露的Activity对象就会一直存在于应用的进程中,不会被垃圾回收器回收。
static Activity activity; //这种代码要避免
1
单例模式保存Activity
在单例模式中,Activity经常被用到,那么在内存中保存一个Activity实例是很实用的。但是由于单例的生命周期是这个应用的生命周期,那么会强制延长Activity的生命周期,是相当危险的,所以无论如何都不能在单例中保存类似Activity的对象。
public class Singleton {
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context context){
        this.mContext = context;
    }

    public static Singleton getInstance(Context context){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在调用Singleton的getInstance()方法时传入了Activity,那么当instance没有释放的时,这个Activity会一直存在,导致内存泄漏。解决方法:

可以将new Singleton(context)改为new Singleton(context.getApplicationContext())即可,
这样便和传入的Activity没关系了。
1
2
Static View
同理,静态的View也是不建议的。
Inner Classes
内部类的优势是可以提高可读性和封装性,而且可以访问外部类,倒霉的是,导致内存泄漏的原因,就是内存类持有对外部类实例的强引用,例如在内部类中持有对Activity对象,解决方法:
1.将内部类变成静态内部类;
2.如果有强引用的Activity属性,则将该属性的引用方式改为弱引用;
3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;
1
2
3
静态内部类不需要外部类的实例,可以通过外部类 . 静态内部类使用。

     //这儿发生泄漏    
     public void test() {    
         new Thread(new Runnable() {      
             @Override
             public void run() {        
                 while (true) {          
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         }).start();
     }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解决办法:

   //加上static,变成静态匿名内部类
     public static void test() {    
         new Thread(new Runnable() {     
             @Override
             public void run() {        
                 while (true) {          
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         }).start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
内部类引起内存泄漏的原因
https://blog.csdn.net/qq_22706515/article/details/51321718
5. Anonymous Classes
匿名类也维护了外部类的引用。当你在匿名类中执行耗时任务时,如果用户退出,会导致匿名类持有的Activity实例不会被垃圾回收器回收,直到异步任务结束。
6. Handler
handler中,Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。解决办法:

1.可以把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露;
2.如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的.
3.在界面销毁是,释放handler资源
@Override
 protected void doOnDestroy() {        
     super.doOnDestroy();        
     if (mHandler != null) {
         mHandler.removeCallbacksAndMessages(null);
     }
     mHandler = null;
     mRenderCallback = null;
 }
1
2
3
4
5
6
7
8
9
10
11
12
同样还有其他匿名类实例,如TimerTask、Threads等,执行耗时任务持有Activity的引用,都可能导致内存泄漏。
线程产生内存泄漏的主要原因在于线程的生命周期的不可控,如果我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,所以它引用的老的Activity也不会被销毁,因此就出现内存泄漏的问题。
要解决Activity的长期持有造成的内存泄漏,可以通过以下方法:
传入Application的Context,因为Application的生命周期就是整个应用的生命周期,所以没有任何问题。
如果此时传入的Activity的Context,当这个Context所对应的Activity退出时,主动结束执行的任务,释放Activity资源。
将线程的内部类改为静态内部类
因为非静态内部类会自动持有一个所属类的实例,如果所属类的实例已经结束生命周期,但内部类的方法仍在执行,就会hold其主体(引用),就使主体不能被释放,就造成内存泄漏,静态类编译后和非内部类是一样的,有自己的类名,不会引用所属类的实例,所以就不会造成泄漏。
如果需要引用Activity,使用弱引用
谨慎对context使用static关键字
2、Bitmap没有调用recycle()
Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后在设置为null

3、集合中对象没有清理造成的内存泄漏
我们经常把一些对象的引用加入到集合中,但我们不需要改对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static,那情况就更加严重了。解决方法:

在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。
1
4、注册没有取消造成的内存泄漏
这种Android内存泄漏比Java的内存泄漏还要严重,因为其他一些Android程序可能引用系统的Android程序的对象(如注册机制)。即使Android程序已经结束,但是别的应用程序仍然还有对Android程序的某个对象的引用,也会造成内存泄漏。解决方法:

1.使用ApplicationContext代替ActivityContext;
2.在Activity执行onDestory时,调用反注册;
1
2
5、资源对象没关闭造成的内存泄漏
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。而不是等待GC来处理。

6、占用内存较多的对象(图片过大)造成内存泄漏
因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示多张那问题就更显著了。Android分配给Bitmap的大小只有8M。解决方法:

等比例缩小图片
    BitmapFactory.Options options = new BitmapFactory.Options(); 
    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一 
1
2
对图片采用软引用,及时地进行recycle()操作
//软引用
 SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap); 
//回收操作
 if(bitmap != null) {  
       if(bitmap.get() != null && !bitmap.get().isRecycled()){ 
           bitmap.get().recycle(); 
           bitmap = null;  
       } 

1
2
3
4
5
6
7
8
9
7、WebView内存泄漏
WebView造成内存泄漏的原因

解决方法:

@Override
protected void onDestroy() {
    if( mWebView!=null) {

        // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
        // destory()
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }

        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        mWebView.destroy();

    }
    super.on Destroy();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
检测内存泄漏的方法
1. 使用 静态代码分析工具-Lint 检查内存泄漏
Lint 是 Android Studio 自带的工具, Analyze -> Inspect Code 然后选择想要扫面的区域即可


对可能引起泄漏的编码,Lint 都会进行温馨提示:


2. LeakCanary 工具

Square 公司出品的内存分析工具,官方地址如下:https://github.com/square/leakcanary/LeakCanary 需要在项目代码中集成。当内存泄漏发生时,LeakCanary 会弹窗提示并生成对应的堆存储信息记录。

3. Android Profiler分析器
打开Android Studio,编译代码,在模拟器或者真机上运行App,然后点击(如下),进入如下界面


点击MEMORY,进入


AndroidStudio3.0最新 Android Profiler分析器(cpu memory network 分析器)

如上图所示,内存分析器的默认视图包括以下内容:

① 强制执行垃圾收集事件的按钮。

② 捕获堆转储的按钮。

③ 记录内存分配的按钮。

④ 放大时间线的按钮。

⑤ 跳转到实时内存数据的按钮。

⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。

⑦ 内存使用时间表,其中包括以下内容:

每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。

虚线表示已分配对象的数量,如右侧y轴所示。

每个垃圾收集事件的图标。
————————————————
版权声明:本文为CSDN博主「哈哈云」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_31010739/article/details/88574481

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值