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

最近项目中频繁出现OOM的问题,各种路径测试、内存走向分析、各种逻辑推理才最终定位到问题。在这过程中和组内的同学讨论的时候发现有的同学对内存泄漏和内存溢出的概念理解不到位,导致沟通过程比较尴尬。很多同学对这两个概念理解不够透彻,在项目中频繁写出内存泄漏的低级代码出来。结合自己的理解我写一篇文章理解下这两个概念。

内存泄漏

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

内存泄漏.png

内存溢出

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

内存溢出

二者关系

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

内存泄漏与溢出.png

内存泄漏经典模型

静态变量

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

 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;
}

内部类

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

内部类循环.png

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

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都泄漏了。

内部类被其他对象持有.png

这种模式最常见的就是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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    宇宙只有巴掌大阅读 2,357评论 0 12
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    _痞子阅读 1,611评论 0 8
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    DreamFish阅读 789评论 0 5
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    apkcore阅读 1,214评论 2 7
  • 人不能没有良心,静下心来想想我的朋友,自从你离开家门口到现在为止,你只要稍加努力,你都可以每个月多挣五十,一百、乃...
    汤程耀阅读 692评论 0 0