【转载】Android 内存溢出如何发生的。
【转载】Android 内存溢出如何发生的。
且谈Android内存溢出
前言
关于android的内存溢出在创新文档库中也有不少,网络上也有很多这方面的资料.所以这遍文章不算是正真意义上的创新,仅仅只是对xxx源码中出现的一些android开发中容易犯错的代码习惯一次总结.
1. Android的内存溢出是如何发生的
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M.因此我们所能利用的内存空间是有限的.如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误.
原因主要有两个: l 由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放. l 保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制.
2. Static
static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例.所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多)就要谨慎对待了.例如xxx源码:
public class IntentMapping { private static Contextcontext; //. . . }
如果将Activity赋值到么context的话.那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.所以这里就潜在内存溢出.
再看android文档中的一个例子:
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);
}
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<Context> mContextRef;
在这里要提到,在xxx中其实是有在ApplicationInit中将baseContext缓存下来,但可惜的是并没有很好的利用,而是在各自的模块中又缓存了自身的Context例如上面例子中的IntentMapping.
3. 线程
程产生内存泄露的主要原因在于线程生命周期的不可控.看看下面这段代码.
public class Pandareader extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
` new Thread() { public void run() {
InitDataBase.initDB(Pandareader.this.getBaseContext());
//. . .
}
}
}
这段代码很平常也很简单,也是我们经常使用的形式.但存在一个问题:假设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函数中,手动关闭.