内存溢出和内存泄漏

1、内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

2、内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致out of memory!

内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。 
    内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出!比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出. 
   以发生的方式来分类,内存泄漏可以分为4类: 
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。 
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。 
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。 
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到 

且谈Android内存溢出

前言

    关于android的内存溢出在创新文档库中也有不少,网络上也有很多这方面的资料.所以这遍文章不算是正真意义上的创新,仅仅只是对xxx源码中出现的一些android开发中容易犯错的代码习惯一次总结.

1.  Android的内存溢出是如何发生的

 Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M.因此我们所能利用的内存空间是有限的.如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误.原因主要有两个:
 由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放.
 保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制.

2.  Static

static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例.所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多)就要谨慎对待了.例如xxx源码:

<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class IntentMapping {
    private static Contextcontext;
    //. . .
}</span></span>

如果将Activity赋值到么context的话.那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.所以这里就潜在内存溢出.

再看android文档中的一个例子:  

<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">private static Drawable sBackground;
 
    @Override
    protected void onCreate(Bundle state){
       super.onCreate(state);
       TextView label = new TextView(this);
       label.setText(“Leaks are bad”);
       if(sBackground == null){
           sBackground = getDrawable(R.drawable.large_bitmap);
       }
       label.setBackgroundDrawable(sBackground);
       setContentView(label);
}</span></span>

sBackground,是一个静态的变量,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用.这个引用链如下:

Drawable->TextView->Context

所以,最终该Context也没有得到释放,发生了内存泄露,该部分的详细内容也可以参考Android文档中Article部分.

有效的避免内存溢出的方法:

1.应该尽量避免static成员变量引用资源耗费过多的实例,比如Context.

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

3.使用WeakReference代替强引用.比如可以使用WeakReference mContextRef;

在这里要提到,在xxx中其实是有在ApplicationInit中将baseContext缓存下来,但可惜的是并没有很好的利用,而是在各自的模块中又缓存了自身的Context例如上面例子中的IntentMapping.

3.  线程

程产生内存泄露的主要原因在于线程生命周期的不可控.看看下面这段代码.

<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class Pandareader extends BaseActivity {
@Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    `   new Thread() {
           public void run() {
          InitDataBase.initDB(Pandareader.this.getBaseContext());
           //. . .
        }
    }
}</span></span>

这段代码很平常也很简单,也是我们经常使用的形式.但存在一个问题:假设Thread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,这时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此.

由于我们的线程是Activity的内部类,所以Thread中保存了Activity的一个引用,当Thread的run函数没有结束时,Thread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此潜在内存泄露.

Android提供的AsyncTask同样也会潜在内存溢出的风险.因为Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露.

要避免线程的内存溢出要到2点:

1.  将线程的内部类,改为静态内部类.

2.  在线程内部采用弱引用保存Context引用.

4.  Bitmap

可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题.因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示或创建多张那OutOfMemory问题就更显著了.而恰恰xxx的文本阅读就是依赖于PageBitmap. PageBitmap是创建屏幕分辨率大小的Bitmap,虽然有封装了一个池BitmapPool来管理PageBitmap的创建和销毁,以及引用.但是还是没发完全避免由于PageBitmap所带来的内存溢出.并且TextViewerActivity, TextDraw, TXTParagraph, ParagraphLineData, ParagraghData, TXTReader之间错综复杂的调用关系以及引用,决定了在TextViewerActivity中创建的对象并没有完全的释放,容易发生内存溢出.

有效的解决内存溢出:

1.及时的销毁.

虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制.因此,在用完Bitmap时,要及时的recycle掉.recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:”该图片可以释放了”.

2.设置一定的采样率.

有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存.

但像上面xxx中出现的问题,并不能简单的应用采样率来解决.那就要更深一步分析Bitmap在TextDraw中使用的情况,包括BitmapPool中缓存PageBitmap数量以及对象的引用情况.

5.  Cursor

Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉.然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理.并且Android明显是倾向于手动的将Cursor close掉. 有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭.

且谈内心泄露

什么是内存泄露
Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

Android中使用Handler造成内存泄露的原因

<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">Handler mHandler = new Handler() {
    @Override
    publicvoidhandleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}</span></span>

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

内存泄露的危害
只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

使用Handler导致内存泄露的解决方法
方法一:通过程序逻辑来进行保护。

1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。 
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。 

方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">static class MyHandler extends Handler {
    @Override
    publicvoidhandleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }</span></span>
<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-size:18px;">}</span></span></span>

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">static class MyHandler extends Handler {
    WeakReferencemActivityReference;
 
    MyHandler(Activityactivity) {
        mActivityReference=newWeakReference(activity);
    }
 
    @Override
    publicvoidhandleMessage(Message msg) {
        finalActivity activity = mActivityReference.get();
        if(activity != null){
            mImageView.setImageBitmap(mBitmap);
        }
    }
}</span></span>

将代码改为以上形式之后,就算完成了。

延伸:什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anonymous-OS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值