Android之内存泄漏与内存溢出

Android之内存泄漏与内存溢出

在这里插入图片描述

概览

内存泄漏(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。

内存溢出 (out of memory):是指程序申请内存时,没有足够的内存供申请者使用,导致数据无法正常存储到内存中。也就是说给你个int类型的存储数据大小的空间,但是却存储一个long类型的数据,这样就会导致内存溢出。

内存溢出和内存泄露的关系以及区别

关系:内存泄露最终会导致内存溢出,由于系统中的内存是有限的,如果过度占用资源而不及时释放,最后会导致内存不足,从而无法给所需要存储的数据提供足够的内存,从而导致内存溢出。导致内存溢出也可能是由于在给数据分配大小时没有根据实际要求分配,最后导致分配的内存无法满足数据的需求,从而导致内存溢出。

区别:内存泄露是由于GC无法及时或者无法识别可以回收的数据进行及时的回收,导致内存的浪费;内存溢出是由于数据所需要的内存无法得到满足,导致数据无法正常存储到内存中。内存泄露的多次表现就是会导致内存溢出。

内存泄漏

  • 从有一组定义为gc root的根节点到目标对象的路径,称为可达性。此类对象也就是存活对象,不可达的对象就是应该被gc垃圾回收机制进行回收的对象。在当前应用的生命周期内不再使用的对象,依然被gc root引用,导致无法回收,既造成了内存泄漏。
  • 内存泄漏即 ML (Memory Leak) 指 程序在申请内存后,当该内存不需再使用;但 却无法被释放&归还给程序的现象。

内存泄漏带来的危害

  • 用户对单次的内存泄漏并没有什么感知,但当可用的空闲空间越来越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能会造成界面卡顿等情况。
  • 后续需要分配内存的时候,很容易导致内存空间不足而出现 OOM(内存溢出)。

常见的内存泄漏问题

    1. 资源性对象未关闭
      • 对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。
      • 例如:
        • 文件I/O流、数据库连接(SQLiteDatabase)、媒体资源(MediaPlayer)、网络连接(HttpURLConnection)、Cursor对象、ContentResolver、蓝牙连接(BluetoothSocket)、网络套接字(Socket)、Bitmap对象未关闭。
        • TimerTask定时任务、Timer计时器未取消。
        • WebView未销毁。
        • 线程池未关闭。
      • 以上所提及的资源应该在使用完或者在Activity页面销毁时及时关闭。
    1. 注册对象未注销各种Listener
      • 订阅者模式中,如果注册对象不再使用时,未及时注销,会导致订阅者列表中维持这对象的引用,阻止垃圾回收,导致内存泄露。
      • 例如:
        • BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
    1. 类的静态变量持有大数据对象
      • 尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。
    1. 单例造成的内存泄漏
      • 优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。
      •  public class Singleton {
             private static Singleton instance;
             private Context mContext;
             private Singleton(Context context){
                 this.mContext = context;
             }
        
         	/**
         		* 如果传入的context是activity,service的上下文,会导致内存泄漏
         		* 原因是我们的instance是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长
         		* 当activity销毁的时候,我们的这个instance仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏
             */
             public static Singleton getInstance(Context context){
                 if (instance == null) {
                     synchronized (Singleton.class){
                         if (instance == null){
                             instance = new Singleton(context);
                         }
                     }
                 }
                 return instance;
             }
         }
        
    1. 非静态内部类的静态实例
      • 非静态内部类持有外部类实例的引用,若非静态内部类的实例是静态的,便拥有app存活期整个生命周期,长期持有外部类的引用,阻止外部类实例被回收。
      • 使用内部类的情况十分常见,尤其是匿名内部类:一些接口的匿名实现类,都是内部类。
      • 解决方案:
        • (1)改为静态内部类,不再持有外部类实例的引用。
        • (2)避免申明非静态内部类的静态实例。
        • (3)将内部类抽取出来封装成一个单例,如果需要Context,没有特殊要求就使用Application Context;如果需要Activity Context,则使用完毕置空,或者使用弱引用。
    1. Handler造成的泄漏
      • Handler造成内存泄露的原因:非静态内部类或者匿名内部类使得Handler默认持有外部类的引用。在Activity销毁时,由于Handler可能有未执行完/正在执行的Message,导致Handler持有Activity的引用,进而导致GC无法回收Activity。
      • 解决办法:
        • 静态内部类+弱引用:
          •  private static class MyHandler extends Handler{
             	private final WeakReference<MineActivity> mMineActivityWeak;
             	public MyHandler(MineActivity mineActivity){
             		mMineActivityWeak = new WeakReference<>(mineActivity);
             	}
             	@Override
             	public void handleMessage(@NonNull Message msg) {
             		super.handleMessage(msg);
             		MineActivity mineActivity = mMineActivityWeak.get();
             		if(mineActivity != null){
             			mineActivity.number = 5;
                     }
                 }
             }
            
        • Activity销毁时,清空Handler中,未执行或正在执行的Callback以及Message。
          •   // 清空消息队列,移除对外部类的引用
             @Override
             protected void onDestroy() {
               	super.onDestroy();
               	mHandler.removeCallbacksAndMessages(null);
             }
            
      • 注:AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。
        • 解决办法:
          • ①静态内部类+弱引用。
          • ②在 Activity 或 Fragment 的 onDestroy() 方法中,手动取消 AsyncTask。
            •  @Override
               protected void onDestroy() {
                   super.onDestroy();
                   staticAsyncTask.cancel(true);
               }
              
          • ③使用 AsyncTaskLoader,它是一个专门设计用来避免内存泄露的异步任务处理器。它会在 Activity 或 Fragment 销毁时自动取消所有的任务,从而避免内存泄露。
    1. 集合类持有过多对象导致的泄漏
      • 集合类持有过多对象可能导致内存泄漏的原因是因为集合中的对象如果变成无用状态,但是由于被集合所持有,其内存不会及时被回收,导致内存泄漏。
      • 常见的容器类还有线程池、对象池、图片缓存池等。
      • 例如:
        • 如果一个 Activity 中持有了一个集合对象,如果该 Activity 被销毁了但是集合中的对象没有移除或者清空,那么这些对象将无法被回收,导致内存泄漏。
      • 解决办法:
        • 在集合中存储对象时,要注意及时移除或者清空集合中不再需要的对象,尽量减少无用对象在集合中存储的时间。最简单的方法是:清空集合对象并设置为null。
          •  @Override
             protected void onDestroy() {
                 mList.clear();
                 mList = null;
                 super.onDestroy();
             }
            
        • 使用弱引用的集合,将集合中的对象与应用程序的生命周期解耦,避免持有过多无用对象导致的内存泄漏问题。
    1. WebView导致的泄漏
      • 目前Android中WebView的实现存在很大的兼容性问题,Google支持各个ROM厂商自行定制自己的WebView实现,各个ROM间差异较大,且大多都存在内存泄露问题。除了调用其内部的clearCache()、clearHistory()、removeAllViews()、freeMemory()、destroy()和置null以外,一般比较粗暴有效的解决方法是:将包含WebView的Activity放在一个单独的进程中,不需要时将进程销毁,从而释放所有所占内存。
      • 解决办法:
        • 不在 xml 中定义 Webview ,这样会引用 Activity,而是在需要的时候在 Activity 中创建,并且使用 getApplicationgContext()。
          •  LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
             mWebView = new WebView(getApplicationContext());
             mWebView.setLayoutParams(params);
             mLayout.addView(mWebView);
            
        • Activity 关闭时需要手动释放 Webview 内存。
          •  override fun onDestroy() {
                 // webview?.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
                 // webview?.clearView()
                 webview?.loadUrl("about:blank")
                 webview?.parent?.let {
                     (it as ViewGroup).removeView(webview)
                 }
                 webview?.stopLoading()
                 webview?.settings?.javaScriptEnabled = false
                 webview?.clearHistory()
                 webview?.clearCache(true)
                 webview?.removeAllViewsInLayout()
                 webview?.removeAllViews()
                 webview?.webViewClient = null
                 webview?.webChromeClient = null
                 webview?.destroy()
                 webview = null
                 super.onDestroy()
             }
            
    1. 使用ListView时造成的内存泄漏
      • 使用ListView时,如果不正确地使用Adapter,可能会导致内存泄漏。这是因为ListView的机制,它会在滚动过程中重用convertView,如果在convertView中持有了一些对象的引用并没有及时释放,就会导致内存泄漏。
        •  @Override
           public View getView(int position, View convertView, ViewGroup parent) {
               ViewHolder holder;
               if (convertView == null) {
                   convertView = mInflater.inflate(R.layout.list_item_layout, null);
                   holder = new ViewHolder();
                   holder.titleTextView = convertView.findViewById(R.id.title_text_view);
                   holder.contentTextView = convertView.findViewById(R.id.content_text_view);
                   convertView.setTag(holder);
               } else {
                   holder = (ViewHolder) convertView.getTag();
               }
               return convertView;
           }
          
           private static class ViewHolder {
               TextView titleTextView;
               TextView contentTextView;
           }
          
    1. 使用第三库传递context
      • 在项目中经常会使用各种三方库,有些三方库的初始化需要我们传入一个 Context 对象,尽量使用 Context.getApplicationContext,不要直接将 Activity 传递给其他组件。
      • 比如:在一些广告的SDK中,它可能要求要用Activity的Context,但是还是要尽量查阅资料或者咨询一下看能不能使用Application的Context。
    1. 静态View导致内存泄露
      • 有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。
      • 解决办法:
        • 在使用静态View时,需要确保在资源回收时,将静态View detach掉。
    1. 属性动画未及时关闭导致内存泄露
      • 在使用ValueAnimator或者ObjectAnimator时,如果没有及时做cancel取消动画,就可能造成内存泄露。 因为在cancel方法里,最后调用了endAnimation(); ,在endAnimation里,有个AnimationHandler的单例,会持有属性动画对象的引用
      • 解决办法:
        • 在在onDestory时,调用动画的cancel方法

内存泄漏检测

  • 内存泄漏检测神器LeakCanary。
    • LeakCanary 是 Square 公司的一个开源库。通过它可以在 App 运行过程中检测内存泄漏,当内存泄漏发生时会生成发生泄漏对象的引用链,并通知程序开发人员。
    • 原理:LeakCanary 是通过在 Application 的 registerActivityLifecycleCallbacks 方法实现对 Activity 销毁监听的,该方法主要用来统一管理所有 Activity 的生命周期。所有 Activity 在销毁时在其 OnDestory 方法中都会回调 ActivityLifecycleCallbacks 的 onActivityDestroyed 方法,而 LeakCanary 要做的就是在该方法中调用 RefWatcher.watch 方法实现对 Activity 进行内存泄漏监控。
    • 接入:
      • 添加依赖:
        •  dependencies {
           	// debugImplementation because LeakCanary should only run in debug builds.
              debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
           }
          
    • 2.0以上版本会自己启动。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android开发中,内存溢出是指应用程序在申请的内存超过了系统可用内存的情况。这可能导致应用程序崩溃、运行缓慢或者被系统强制关闭。以下是一些常见导致内存溢出的情况和解决方法: 1. Bitmap内存溢出:在使用Bitmap处理图片时,如果没有正确地释放Bitmap对象,或者加载了过大的图片导致占用大量内存,就会发生内存溢出。解决方法包括使用合适的图片加载库(如Glide、Picasso),及时释放不再使用的Bitmap对象,或者对图片进行压缩处理。 2. 长时间占用内存的后台任务:如果应用程序中存在长时间运行的后台任务,而且这些任务没有及时释放占用的内存,就可能导致内存溢出。解决方法是在合适的时机停止或取消后台任务,并释放相关资源。 3. 内存泄漏内存泄漏问题在上一个问题中已经提到过。如果应用程序中存在内存泄漏,持续占用内存而不释放,就会导致内存溢出。解决方法是检查代码,及时释放不再使用的对象引用,避免长时间持有上下文或其他对象的引用。 4. 大量对象的创建和销毁:如果应用程序频繁地创建和销毁大量对象,而没有及时释放,就会导致内存溢出。解决方法包括使用对象池或缓存来重复利用对象,减少对象的创建和销毁次数。 5. 不适当的资源使用:如果应用程序使用了大量的资源,如文件、数据库连接等,而没有正确地关闭或释放这些资源,就会导致内存溢出。解决方法是在不再需要使用资源的地方及时关闭或释放资源。 6. 大数据集的处理:如果应用程序需要处理大量的数据集,而没有进行分页或分批加载,就可能导致内存溢出。解决方法是采用分页加载或分批处理的方式,减少一次性加载大量数据的压力。 总之,要避免Android应用程序中的内存溢出问题,开发者应该注意及时释放占用的内存,避免内存泄漏和不适当的资源使用。合理管理Bitmap对象、后台任务、对象的创建和销毁,以及大数据集的处理都是预防内存溢出的重要方法。此外,使用工具进行内存分析和优化也是提高应用程序性能的有效方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值