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

简介:

最近项目中频繁出现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);
目录
相关文章
|
30天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
39 8
|
2月前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
2月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
52 6
|
2月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
351 9
|
6月前
|
存储 设计模式 监控
运用Unity Profiler定位内存泄漏并实施对象池管理优化内存使用
【7月更文第10天】在Unity游戏开发中,内存管理是至关重要的一个环节。内存泄漏不仅会导致游戏运行缓慢、卡顿,严重时甚至会引发崩溃。Unity Profiler作为一个强大的性能分析工具,能够帮助开发者深入理解应用程序的内存使用情况,从而定位并解决内存泄漏问题。同时,通过实施对象池管理策略,可以显著优化内存使用,提高游戏性能。本文将结合代码示例,详细介绍如何利用Unity Profiler定位内存泄漏,并实施对象池来优化内存使用。
380 0
|
6月前
|
存储 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
75 0
|
6月前
|
存储 算法 安全
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
81 0
|
4月前
|
Java
在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
【9月更文挑战第25天】在ArkTS中,有效进行内存管理并避免内存泄漏的方法包括:及时释放不再使用的资源,如关闭监听器和清理定时器;避免循环引用,通过弱引用打破循环;合理使用单例模式,确保单例对象正确释放;及时处理不再使用的页面和组件,在卸载时清理相关资源。
150 9
|
4月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
4月前
|
Arthas 监控 Java
监控线程池的内存使用情况以预防内存泄漏
监控线程池的内存使用情况以预防内存泄漏

热门文章

最新文章