在Android实际开发中,程序员们经常会遇到内存泄露(Out Of Memory),说白了,就是内存不够用了。导致应用程序运行很卡,甚至直接闪退。为什么会出现内存溢出呢?因为在Android手机中,每个应用一般默认只能使用16M内存(当然这个16M不是固定的,一般是由手机商家设定),如果超过这个限制就是出现很快或者闪退。
在Android中几种常的OOM包括以下几种:
一、static修饰词引起的内存泄露
static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
public class ClassName {
private static Context mContext;
}
以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.
如何才能有效的避免这种引用的发生呢?
第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;
这里说的弱引用(WeakReference)、软引用(softReference)以及强引用?我们先来简单比较下:
强引用:String str="helloworld";就是我们经常用的,当内存不足时,不会回收这个对象。
弱引用 :ObjectA a = newObjectA();
ObjectB b = new ObjectB(a);
a = null;
在上面我们先初始化了一个ObjectA对象,再初始化了一ObjectB,需要注意的是,在初始化ObjectB,需要传递一个ObjectA对象,注意这样ObjectB就对ObjectA有一个引用。当ObjectA对象的引用a置空了,a不再指向对象ObjectA的地址(a = null) 时,我们都知道当一个对象不再被其他对象引用的时候,是会被GC回收的。在这样很显然是ObjectA是不对被回收的,因为ObjectB依然引用ObjectA,这样就造成了内存泄露。
我们稍微改动下
ObjectA a = new ObjectA();
WeakReference wr = new WeakReference(a);//弱引用,对一些不需要长期引用的对象,可以用这个,当内存不足的时候,gc会回收这个对象
//ObjectB b = new ObjectB(wr.get());
if(wr.get==null){
//对象ObjectA被回收了
}
使用WeakReference 、softReference可以有效保证在内存不足的时候,引用的对象会被gc回收。值得注意的是:当程序中需要一直保持的对象不能使用弱引用或软引用。
二、线程Thread、AsyncTask引起的内存泄露
private
class MyThread extends Thread{
@Override
public void run() {
super.run();
//do somthing
}
new
AsyncTask<Void, Void, UserModel>() {
@Override
protected UserModel doInBackground(Void... params) {
do somthing
}
相信这两个线程大家在熟悉不过了,但是注意是错的,很容易造成内存泄露。假设在线程中需要处理很耗时的操作
将设备的竖屏变为了横屏,当屏幕转换时会重新创建Activity(Activity应该会被销毁并重新初始化,执行oncreate方法),然而事实上并非如此。
由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
如何避免线程造成的内存泄露呢?
将线程变成静态的内部类,同事,在线程内部采用弱引用当前Context。 如下
class MainActivity extends Activity{
private
Textview tv;
.......
private
static class MyThread extends Thread {
WeakReference<MainActivity> mThreadActivityRef;
public MyThread(MainActivityactivity) {
mThreadActivityRef = new WeakReference<MainActivity>(
activity);
}
@Override
public void run() {
super.run();
if (mThreadActivityRef == null)
return;
if (mThreadActivityRef.get() != null)
mThreadActivityRef.get(). tv.setText("sssss");// 不能将Textview 变成static
,上面已经说过了
}
}
}
三、bitmap 引起的内存泄露:
在使用bitmap要注意两点
第一、及时的销毁。
虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
第二、设置一定的采样率。
有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);
四、Handler 引起的内存泄露
handler 大家肯定不会陌生,经常来用来更新主UI,但是用的不好,同样会造成内存溢出。比如Handler.sendEmptyMessageDelayed 的时候,由于该动作不会立即执行,此时如果activity被关闭或者横竖屏切换的时候,handler里面有对activity的引用,所以activity不会被内存回收,造成内存溢出。
正确的做法是在ondestory方法中调用 Handler.removeCallbacksAndMessages(null);在这里顺便说一下,Handler.sendEmptyMessage(0);不断发空消息,可以实现循环的效果,效率比for循环、while循环高。
五、Cursor引起的内存泄露
Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉 。
六、构造Adapter时,没有使用缓存的 convertView 而造成内存泄露:
以构造ListView的BaseAdapter为例,在BaseAdapter中提高了方法:初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list
item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh = null;
if (convertView == null) {
vh = new ViewHolder();
convertView = inflater.inflate(R.layout.mysearchtuijian, null);
vh.tv = (TextView) convertView.findViewById(R.id.mtext);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
public class ViewHolder {
public TextView tv;
}//内部类
}
七、universal-image-loader 造成的内存泄露:
universal-image-loader是用于图片异步加载,使用universal-image-loader要注意几点避免内存溢出:
1、threadPoolSize 配置线程池的大小:1~5 建议用3
2、禁用在内存中缓存 cacheInMemory()
3、在显示选项中使用 .bitmapConfig(Bitmap.Config.RGB_565) . RGB_565模式消耗的内存比ARGB_8888模式少两倍.
4、在显示选项中使用.imageScaleType(ImageScaleType.EXACTLY) 或 .imageScaleType(ImageScaleType.IN_SAMPLE_INT)
5、避免使用 RoundedBitmapDisplayer. 调用的时候它使用ARGB-8888模式创建了一个新的Bitmap对象来显示,对于内存缓存模式 (ImageLoaderConfiguration.memoryCache(...)) 你可以使用已经实现好的方法.
以上7个都是比较常见的。在实际开发中,遇到OOM了,要学会借助工具去分析。
内存分析工具 DDMS --> Heap
一、打开DDMS,选择你的项目名称,点击 uodate Heap,在点击 Cause GC:当于向虚拟机请求了一次gc操作。
Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data
object一行中有一列是“Total Size”,一般情况下Total Size的值2.2~2.8之间,而当其值超过3.55后进程就会被kill。
运行程序,观察Total Size的值,发生内存泄露,Total Size的值越来越大时,按下“Dump HPROF file”按钮,如下
这个时候会提示设置hprof文件的保存路径。
二、转化hprof文件
刚才导出的文件需要用工具转化下。使用AndrodiSDK/tools/hprof-conv转化hprof文件,
首先,要通过控制台进入到你的 android sdk tools 目录下。这里out.hprof文件和tools在同一目录下
cmd:hprof-conv out.hprof out777.hprof
三、打开MAT工具(这个工具要先下载,网上有)。
打开转换后的 hprof 文件,就能看到完整的内存使用分析报告了。
点击左侧的Histogram,在输入你的包名进行过滤
这里我们看到DiQuModel在内存中135个,右键单击,选择Merge Shortest Paths to GC Root ------> exclud all phantom/weak/soft etc .referencer 可以看到内存中这个model的引用情况
得到这些后,在进行分析。
另外:
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。
当然对于内存吃紧来说还可以通过手动干涉GC去处理
注意了,这个设置dalvik虚拟机的配置的方法对Android4.0 设置无效。
这就是我对Android的oom的一点看法,希望能帮到你。
.