android OOM异常分析

来源:互联网 发布:好用的外文数据库 编辑:程序博客网 时间:2024/05/02 00:31
Android 的虚拟机是基于寄存器的 Dalvik,它的最大堆大小一般是 16M,有的机器为 24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory 的错误。
内存溢出的几点原因:

1. 资源释放问题

      程序代码的问题,长期保持某些资源,如 Context、Cursor、IO 流的引用,资源得不到释放造成内存泄露。
2. 对象内存过大问题

     保存了多个耗用内存过大的对象(如 Bitmap、XML 文件),造成内存超出限制。

3. static 关键字的使用问题

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

      public class ClassName {

                  private static Context mContext;

                  //省略

            }

以上的代码是很危险的,如果将 Activity 赋值到 mContext 的话。那么即使该 Activity 已经onDestroy,但是由于仍有对象保存它的引用,因此该 Activity 依然不会被释放。我们举 Android 文档中的一个例子。


private static Drawable sBackground;
@Override
protected voidonCreate(Bundle state) {
super.onCreate(state);
TextView label =new TextView(this); //getApplicationContext
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 也没有得到释放,发生了内存泄露。

针对 static 的解决方案

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

2) Context 尽量使用 ApplicationContext,因为 Application 的 Context 的生命周期比较长,引用它不会出现内存泄露的问题。3) 使 用 WeakReference 代 替 强 引 用 。 比 如 可 以 使 用 WeakReference<Context>mContextRef;

4. 线程导致内存溢出线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

public classMyActivityextends Activity {
@Override
public voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
newMyThread().start();
}
private classMyThreadextends Thread{
@Override
public voidrun() {
super.run();
//do somthing while(true)
}
}
}这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设 MyThread 的 run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一 般情况下当屏幕转换时会重新创建 Activity,按照我们的想法,老的 Activity 应该会被销毁才对,然而事实上并非如此。由于我们的线程是 Activity 的内部类,所以 MyThread 中保存了 Activity 的一个引用,当MyThread 的 run 函数没有结束时,MyThread 是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。有些人喜欢用 Android 提供的 AsyncTask,但事实上 AsyncTask 的问题更加严重,Thread 只有在 run 函数不结束时才出现这种内存泄露问题,然而 AsyncTask 内部的实现机制是运用了ThreadPoolExcutor,该类产生的 Thread 对象的生命周期是不确定的,是应用程序无法控制的,因此如果 AsyncTask 作为 Activity 的内部类,就更容易出现内存泄露的问题。
针对这种线程导致的内存泄露问题的解决方案:(一) 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。(二) 在线程内部采用弱引用保存 Context 引用。

4、构造 Adapter 时,没有使用缓存的 convertView在使用 ListView 的时候通常会使用 Adapter,那么我们应该尽可能的使用 ConvertView。
为什么要使用 convertView?当 convertView 为空时,用 setTag()方法为每个 View 绑定一个存放控件的 ViewHolder 对象。当 convertView 不为空,重复利用已经创建的 view 的时候,使用 getTag()方法获取绑定的ViewHolder 对象,这样就避免了 findViewById 对控件的层层

风; letter-spacing: normal; line-height:4、构造 Adapter 时,没有使用缓存的 convertView在使用 ListView 的时候通常会使用 Adapter,那么我们应该尽可能的使用 ConvertView。
为什么要使用 convertView?当 convertView 为空时,用 setTag()方法为每个 View 绑定一个存放控件的 ViewHolder 对象。当 convertView 不为空,重复利用已经创建的 view 的时候,使用 getTag()方法获取绑定的ViewHolder 对象,这样就避免了 findViewById 对控件的层层查询,而是快速定位到控件。
险和可
,这样就,这样就会给以后的测试和问题排查带来困难和风险。存问题,这样就会给以后的测试和问题排查带来困难和风险。
传智播客武汉校区就业部出品 务实、创新、质量、分享、专注、责任
54
4、构造 Adapter 时,没有使用缓存的 convertView在使用 ListView 的时候通常会使用 Adapter,那么我们应该尽可能的使用 ConvertView。
为什么要使用 convertView?当 convertView 为空时,用 setTag()方法为每个 View 绑定一个存放控件的 ViewHolder 对象。当 convertView 不为空,重复利用已经创建的 view 的时候,使用 getTag()方法获取绑定的ViewHolder 对象,这样就避免了 findViewById 对控件的层层查询,而是快速定位到控件。
5、Bitmap 对象不再使用时调用 recycle()释放内存有时我们会手工的操作 Bitmap 对象,如果一个 Bitmap 对象比较占内存,当它不再被使用的时候,可以调用 Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。
6、其他Android 应用程序中最典型的需要注意释放资源的情况是在 Activity 的生命周期中,在
存问题,这样就会给以后的测试和问题排查带来困难和风险。<

0 0
原创粉丝点击