内存泄漏和内存溢出的总结

现在的应用中内存的管理是一个大的问题,如何保证不因内存问题影响程序的使用和用户体验效果,这里是我看到的网络上的一篇不错的文章,对其进行了简单的整理。让我们来共同学习

内存的重要性:Android主要应用在嵌入式设备当中,而嵌入式设备由于一些众所周知的条件限制,通常都不会有很高的配置,特别是内存是比较有限的。如果我们编写的代码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机。每个应用程序都是在属于自己的进程中运行的。一方面,如果程序在运行过程中出现了内存泄漏的问题,仅仅会使得自己的进程被kill掉,而不会影响其他进程(如果是system_process 等系统进程出问题的话,则会引起系统重启)。另一方面Android为不同类型的进程分配了不同的内存使用上限,如果应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉。

内存泄漏:进程中某些对象(垃圾对象)已经没有价值了,但是它们却可以直接或间接地引用到gc  roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,这就造成了内存泄漏。

造成内存泄漏的可能原因:

(1)     查询数据库没有关闭游标:当我们查询数据库时拿到的游标对象,处理完数据后,不在使用后要及时的关闭游标。

(2)    构造Adapter时,没有使用缓存的 convertView:以构造ListViewBaseAdapter为例,在BaseAdapter中提高了方法:
public View getView(int position, ViewconvertView, ViewGroup parent)

向ListView提供每一个item所需要的view对象。初始时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对象的过程可以查看:
android.widget.AbsListView.java --> voidaddScrapView(View scrap)
方法。
 
示例代码:
public View getView(int position, ViewconvertView, ViewGroup parent) {

  View view = new Xxx(...);
  ... ...
  return view;
}
 修正示例代码:
public View getView(int position, ViewconvertView, ViewGroup parent) {

  View view = null;
  if (convertView != null) {
  view = convertView;
  populate(view, getItem(position));
  ...
  } else {
  view = new Xxx(...);
  ...
  }
  return view;
}  

(3)     Bitmap对象不在使用时调用recycle()释放内存

(4)     释放对象的引用:当我们定义一个成员对象后,并实例化后,使用完毕该对象后,并没有将该对象的引用去除,则该对象会一直占用内存空间,使内存空间减少,这也是内存泄漏。

修改前

public class MainActivity extends Activity {

private TextView num;

private Integer number;

    @Override

    protected voidonCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        num= (TextView) findViewById(R.id.num);

        new Thread(new Runnable(){

               

                @SuppressLint("UseValueOf")@Override

                public void run(){

                       number=new Integer(1);//实例化number

                       android.os.Message msg=newandroid.os.Message();

                     msg.obj=number;//到这里number就使用完毕了,而该对象仍然一直占用着空间

                       handler.sendMessage(msg);

                }

         }).start();

    }

    private Handler handler=new Handler(){

       publicvoid handleMessage(android.os.Message msg) {

              Toast.makeText(MainActivity.this,msg.obj+"", Toast.LENGTH_LONG).show();

       };

    };

}

修改后:

public class MainActivity extends Activity {

private TextView num;

private Integer number;

    @Override

    protected voidonCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        num= (TextView) findViewById(R.id.num);

        new Thread(new Runnable(){

               

                @SuppressLint("UseValueOf")@Override

                public void run(){

                       number=new Integer(1);//实例化number

                       Integero=null;

                       o=number;

                       number=null;//这里就将该对象的引用释放了,保存该对象数据的空间则没有引用指向了,则成为垃圾,会被java的垃圾回收机制回收

                       android.os.Message msg=newandroid.os.Message();

                     msg.obj=o;

                       handler.sendMessage(msg);

                }

         }).start();

    }

    private Handler handler=new Handler(){

       publicvoid handleMessage(android.os.Message msg) {

              Toast.makeText(MainActivity.this,msg.obj+"", Toast.LENGTH_LONG).show();

       };

    };

}

(5)     Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()onStop() onDestroy()方法中需要适当的释放资源的情况。

(6)     静态变量的使用:

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

1.         public class ClassName {  

2.              private static Context mContext;  

3.         }  

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

我们举Android文档中的一个例子。

1.         private static Drawable sBackground;  

2.              

3.          @Override  

4.          protected void onCreate(Bundle state) {  

5.            super.onCreate(state);  

6.              

7.            TextView label = new TextView(this);  

8.            label.setText("Leaks are bad");  

9.              

10.         if (sBackground == null) {  

11.           sBackground = getDrawable(R.drawable.large_bitmap);  

12.         }  

13.         label.setBackgroundDrawable(sBackground);  

14.           

15.         setContentView(label);  

16.       }  

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

    Drawable->TextView->Context

    所以,最终该Context也没有得到释放,发生了内存泄露。

    如何才能有效的避免这种引用的发生呢?

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

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

    第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;

 

(7)     线程的使用造成的内存泄漏

第一种:线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

1.         public class MyActivity extends Activity {  

2.             @Override  

3.             public void onCreate(Bundle savedInstanceState) {  

4.                 super.onCreate(savedInstanceState);  

5.                 setContentView(R.layout.main);  

6.                 new MyThread().start();  

7.             }  

8.           

9.             private class MyThread extends Thread{  

10.              @Override  

11.              public void run() {  

12.                  super.run();  

13.                  //do somthing  

14.              }  

15.          }  

16.      }  

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

    由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThreadrun函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

第二种:异步任务类的使用

AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。

这种线程导致的内存泄露问题应该如何解决呢?

    第一、将线程的内部类,改为静态内部类。

    第二、在线程内部采用弱引用保存Context引用。

    解决的模型如下:

1.         public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends  

2.                 AsyncTask<Params, Progress, Result> {  

3.             protected WeakReference<WeakTarget> mTarget;  

4.           

5.             public WeakAsyncTask(WeakTarget target) {  

6.                 mTarget = new WeakReference<WeakTarget>(target);  

7.             }  

8.           

9.             /** {@inheritDoc} */  

10.          @Override  

11.          protected final void onPreExecute() {  

12.              final WeakTarget target = mTarget.get();  

13.              if (target != null) {  

14.                  this.onPreExecute(target);  

15.              }  

16.          }  

17.        

18.          /** {@inheritDoc} */  

19.          @Override  

20.          protected final Result doInBackground(Params... params) {  

21.              final WeakTarget target = mTarget.get();  

22.              if (target != null) {  

23.                  return this.doInBackground(target, params);  

24.              } else {  

25.                  return null;  

26.              }  

27.          }  

28.        

29.          /** {@inheritDoc} */  

30.          @Override  

31.          protected final void onPostExecute(Result result) {  

32.              final WeakTarget target = mTarget.get();  

33.              if (target != null) {  

34.                  this.onPostExecute(target, result);  

35.              }  

36.          }  

37.        

38.          protected void onPreExecute(WeakTarget target) {  

39.              // No default action  

40.          }  

41.        

42.          protected abstract Result doInBackground(WeakTarget target, Params... params);  

43.        

44.          protected void onPostExecute(WeakTarget target, Result result) {  

45.              // No default action  

46.          }  

47.      }  

 

内存溢出:由于内存占用较大,超出设备的内存大小,就会造成OutOfMemoryOOM),通常手机的内存不会很大,(手机内存分为两部分1.ROM指的是手机本身的内存,跟电脑的硬盘一个道理。2.RAM指的是运行内存,跟电脑内存条石一个道理),代码的不规范很容易造成内存溢出,造成程序被kill。造成内存溢出的原因有:

内存泄漏,长时间占用内存,随着程序的运行,内存占用不断增加,就会造成内存溢出,从而使程序崩溃。

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


猜你喜欢

转载自blog.csdn.net/jiuweitianhu_12345/article/details/52457991