转载

科普:内存泄漏与内存溢出

内存泄漏

内存泄漏是指那些本应该回收(不再使用)的内存对象无法被系统回收的现象。在c++中需要程序猿手动释放内存对象,所以在C++中更容易存在内存泄漏。java引入了自动回收机制,使得在C++中令人头疼的内存问题得到了有效的改善,但这并不意味着java程序员不关注内存,因为垃圾回收机制不能完全保证内存对象在该释放的地方释放,现代java虚拟机中普遍使用根集算法去计算对象的引用可达性,不可达的才能回收,例如下图中的无用对象被有用对象引用着,导致无用对象引用一直可达,系统回收器不敢冒然回收,从而造成内存泄漏。

科普:内存泄漏与内存溢出

内存溢出

系统在为某段执行指令(程序)分配内存的时候,发现内存不足,抛出错误,这叫做内存溢出。

科普:内存泄漏与内存溢出

二者关系

手机设备的内存空间是有限的,为每个应用所分配到的内存空间更是有限的,当内存泄漏对象越来越多,可调配内存空间就越小,App应用性能越差,当可调配的内存空间不够创建新对象时就会引起OOM。

科普:内存泄漏与内存溢出

内存泄漏经典模型

静态变量

静态变量的生命周期是最长的,和应用程序的生命周期一样,当一个大对象被一个类的静态变量引用时,这个对象就无法被系统回收,在应用的整个生命周期中占用内存。常见于工具类,一般中存在大量的工具类,很多同学图方便直接或间接使用静态变量引用一个上下文对象的。

public class NotificationUtil {       //静态变量,notificationManager泄漏       private static  NotificationManager notificationManager;          public static void notification(Context context, Class<?> cls, Message msg) {             if (notificationManager == null){                 notificationManager = (NotificationManager)                    context.getSystemService(context.NOTIFICATION_SERVICE);              }         //....      }     }

NotificationManager 对象泄漏了,同时因为 NotificationManager 对象中有一个上下文对象mContext变量,又回引起启动这个方法的Context对象泄漏。

规避:

对于工具类,如非频繁使用的对象,尽量不要使用 static 变量去引用,可以在方法执行时候再创建,作为局部变量使用;如需要频繁使用,为了提高方法执行效率,对于上面这种情况可以把Context 参数限制为Application 级别的上下文,避免调用方传递Activity级别的上下文,造成Activity泄漏。

public class NotificationUtil {       //静态变量       private static  NotificationManager notificationManager;          public static void notification(Application context, Class<?> cls, Message msg) {            //context限制为Application级别          if (notificationManager == null){                 notificationManager = (NotificationManager)                    context.getSystemService(context.NOTIFICATION_SERVICE);              }         //.....      }     }

单例模式

单例模式其实也是静态变量的一种,单例的生命周期和静态变量时一样的,如果这个单例中持有一个大对象,就会引起这个大对象泄漏。

private static WebViewClient instance;     public static WebViewClient getInstance(Context context) {         if (instance == null) {           //mContext有泄漏风险         instance = new WebViewClient(context, jsToJava);        }         return instance;     }

例如这里的mContext,如果是个Activity的话,会被instance长期引用着的。

规避:

和静态变量一样的道理,尽量使用Application级别的上下文代替Activity级别的上下文。

private static WebViewClient instance;

public static WebViewClient getInstance(Application context) {   

//context限制为Application级别的

if (instance == null) {  

instance = new WebViewClient(context, jsToJava); 

}   

return instance;

}

由于内部类的存在需要依赖它的外部类,由于某些原因导致内部类被引用会无法退出,引起外部类无法回收,这是使用最多也是最容易被用出内存泄漏的了。

科普:内存泄漏与内存溢出

内部类循环或者阻塞,下面就有一个奇葩的代码,一个研究生写的:

import android.content.Context;     import android.util.AttributeSet;     import android.widget.EditText;     public class CheckEditText extends EditText{      public CheckEditText(Context context,AttributeSet attrs){       super(context,attrs);       new Thread(){        @Override        public void run(){           while(true) {              String content = this.getText().toString();              if(content.length() > 12){                 //字符长度不能大于12                 //...              }          }        }       }.start();      }     }

然后所有使用这个CheckEditText的Activity都泄漏了。

科普:内存泄漏与内存溢出

这种模式最常见的就是Handler的使用了,一般项目中很多内存泄漏就是这种模型:

//内部类的Handler,有内存泄漏风险     Handler handler = new Handler() {         public void handleMessage(android.os.Message msg) {             int what = msg.what;             switch (what) {                 case SHOW:                     progressDialog = ProgressDialog.show(DetailActivity.this, null, "图片上传中...");                     break;                 case DISMISS:                     if (progressDialog != null && progressDialog.isShowing()) {                         progressDialog.dismiss();                     }                     break;                 case UPLOADPIC:                     String base64ImageString = (String) msg.obj;                     webView.loadUrl("javascript:fileChooserCallback(" + "'" + base64ImageString                             + "'" + ")");                     break;                  default:                     break;             }          }      };

这是DetailActivity中的一个Handler内部类,这会导致DetailActivity泄漏,因为Handler其实是被一个消息队列引用着。

规避:

对于那些可能长时间执行、阻塞或者被外部引用的内部尽量使用静态内部类代替。静态内部类对象的存在不依附外部类,这样可以避开内部类对外部类的隐性引用,然后使用弱引用持有外部类对象。

static class MyHandler extends  Handler {           //静态内部类代替,并使用若引用持有DetailActivity         private WeakReference<DetailActivity> weakReference;         public MyHandler(DetailActivity activity) {             this.weakReference = new WeakReference(activity);         }         public void handleMessage(android.os.Message msg) {             int what = msg.what;             DetailActivity activity = weakReference.get();             if (activity == null){                 return;             }             switch (what) {                 case SHOW:                     activity.progressDialog = ProgressDialog.show(activity, null, "图片上传中...");                     break;                 case DISMISS:                     if (activity.progressDialog != null && activity.progressDialog.isShowing()) {                         activity.progressDialog.dismiss();                     }                     break;                 case UPLOADPIC:                     String base64ImageString = (String) msg.obj;                     activity.webView.loadUrl("javascript:fileChooserCallback(" + "'" + base64ImageString                             + "'" + ")");                     break;                 default:                     break;             }          }     };     MyHandler handler = new MyHandler(this);
原文  http://www.liuguangli.win/archives/466
正文到此结束
Loading...