单例模式的内存泄漏陷阱

最近项目开发中使用了一个叫做leakcanary的内存泄漏检查工具,当开发中的调试运行时发生内存泄漏,leakcanary会在notification弹出一个内存泄漏报告,最近发生了个内存泄漏并且leakcanary给出了下列报告:

分析下Leakcanary给出的信息,最后一行它说PopOrderActivity这个实例发生了泄漏,即系统gc的时候没有把这个activity给回收(本该回收的,应该是已经退出这个activity了),倒数第二行即说明了有一个叫做PendingOrderManager的类含有这个activity的引用,查看代码,这个PendingOrderManager是个单例,同时它的构造函数传入了一个Context参数:

public class PendingOrderManager {

    private static PendingOrderManager instance;

    private Context mContext;

    public PendingOrderManager(Context context) {

        this.mContext = context;
    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }

...

}

之所以要传入个context是因为这个Manager里面需要创建Preference。

那么现在发生内存泄漏的原因也就很明了了,由于PendingOrderManager是一个单例模式,那么这个类的生命周期就伴随整个应用的生命周期,而它在被PopOrderActivity创建的时候引用了PopOrderActivity,所以当系统GC的时候试图去回收PopOrderActivity时,发现它却在被另一个任然在内存里的PendingOrderManager所引用,所以GC回收它失败,从而导致了内存泄漏。

那么如何解决这个问题呢?答案很简单,在PendingOrderManager中对context的属性使用弱引用即可:

public class PendingOrderManager {

    private static PendingOrderManager instance;

    private WeakReference<Context> wr;

    public PendingOrderManager(Context context) {
        L.d("PendingOrderManager <constructor>");
        wr = new WeakReference<>(context);

    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }

...
}

在PendingOrderManager中原来需要使用Context的地方,用wr.get()即可:

String timesListStr = (String) SPUtils.getPendingOrder(wr.get(), KEY_TIMES_LIST, "");
//这里的wr.get()原来是mContext

这里需要注意的一点是,由于PendingOrderManager这个时候含有的“context”可以被回收置空了,那么后面使用context的地方要注意判断是否为空,即对wr.get的地方注意检查空情况。

还有一种方式可以解决这个问题,考虑到每个使用到PendingOrderManager的地方当都会通过这种方式:

(PendingOrderManager.getInstance(mContext).getXXX()

即每次都能传过来一个当前的调用者的context(肯定不为空),那么在PendingOrderManager的getInstance方法里面除了判定instance是否为空外,最好在判定下wr.get是否为空,这样子若上一个实例化PendingOrderManager的activity被回收后,可以考虑用新的context来重新创建PendingOrderManager的单例。改造后的getInstance方法:

public class PendingOrderManager {

    private static PendingOrderManager instance;

    private WeakReference<Context> wr;

    public PendingOrderManager(Context context) {
        L.d("PendingOrderManager <constructor>");
        wr = new WeakReference<>(context);

    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null || wr.get() == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }

...
}

PS:

leakcanary是个很好的工具,下列是一些参考资料:

http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

时间: 2024-11-20 14:26:14

单例模式的内存泄漏陷阱的相关文章

使用单例模式造成的内存泄漏

Android的单例模式在我们项目开发中经常会用到,不过使用的不恰当的话也会造成内存泄漏.因为单例的静态特性使得单例的生命周期和应用的生命周期一样长, 这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏. Android中习惯使用单例的常见类: xxxManager , xxxHelper , xxxUtils 等 在MainActivity中使用此单例,代码如下: 我们来分析一下,为什么会内存泄漏呢? AppManager a

【转载】Android内存泄漏的8种可能

Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全. 不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak).如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM). 一般内存泄漏(traditional memory leak)的原因

Android开发 |常见的内存泄漏问题及解决办法

在Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了. 内存泄漏有什么影响呢?它是造成应用程序OOM的主要原因之一.由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时

JS内存泄漏 和Chrome 内存分析工具简介(摘)

原文地址:http://web.jobbole.com/88463/ JavaScript 中 4 种常见的内存泄露陷阱 原文:Sebastián Peyrott 译文:伯乐在线专栏作者 - ARIGATO 链接:http://web.jobbole.com/88463/ 点击 → 了解如何加入专栏作者 了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读

浅析c#内存泄漏

一直以来都对内存泄露和内存溢出理解的不是很深刻.在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解. 一.概念 内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给程序的内存,从而造成“Out of memory”之类的错误,使程序不能正常运行. 造成内存溢出有几种情况: 1.计算机本身的内存小,当同时运行多个软件时,计算机得内存不够用从而造成内存溢出.对于这种情况,只能增加计算机内存来解决. 2.软件程序的问题,程序在运行时没能及时释放不用的内存,造成使用的

Android内存泄漏检测-LeakCanary

square/leakcanary udacity/Sunshine-Version-2 添加LeakCanary依赖包 在主模块app下的build.gradle下添加如下依赖: debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' 开启LeakCanary 添加Applicat

C 语言中的指针和内存泄漏

引言 对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧,但是一旦您了解了指针及其关联内存操作的基础,它们就是您在 C 语言中拥有的最强大工具. 本文将与您分享开发人员在开始使用指针来编程前应该知道的秘密.本文内容包括: 导致内存破坏的指针操作类型 在使用动态内存分配时必须考虑的检查点 导致内存泄漏的场景 如果您预先知道什么地方可能出错,那么您就能够小

安卓中的内存泄漏

因为安卓是基于java语言的,所以我们先来看一看java中的内存泄漏,然后在此基础上来谈谈安卓中的内存泄漏. 一java中的内存泄漏: java中的内存泄漏主要是指在堆中分配的内存,明明已经不需要的时候,还仍然保留着访问它的引用,导致GC回收不能及时回收(关于GC回收不做过多赘述),导致这种情况出现的最主要原因是长生命周期的对象持有短生命周期对象的引用,导致短生命周期的对象明明已经不需要却无法被GC回收,从而导致内存泄漏.主要包括以下几种情况: 1在一个类中创建了一个非静态内部类的静态实例,如下

【转】C 语言中的指针和内存泄漏

避免陷阱 级别: 中级 Manish Virmani ([email protected]), 高级软件工程师, IBM 2006 年 10 月 26 日 在使用 C 语言时,您是否对花时间调试指针和内存泄漏问题感到厌倦?如果是这样,那么本文就适合您.您将了解可能导致内存破坏的指针操作类型,您还将研究一些场景,了解要在使用动态内存分配时考虑什么问题. 引言 对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试