欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

性能优化(二) 内存溢出& 内存泄漏

程序员文章站 2022-03-04 23:45:05
...

内存泄漏& 内存溢出

内存泄漏&内存溢出的区别

  • 内存泄漏 memory leak
    • 系统无法释放已经申请的内存
  • 内存溢出 out of memory (内存不足)
    • 申请内存时系统没有足够的内存供其使用
  • 内存泄露是导致内存溢出的原因之一,内存泄漏过多 最终会导致内存溢出
  • 内存泄露可以通过完善代码来避免;内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。

内存泄漏分类

  • 常发性内存泄漏
    • 内存泄漏的代码经常被执行到
  • 偶发性内存泄漏
    • 内存泄漏的代码在某些特定条件下执行
  • 隐式内存泄漏
    • 程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存,多表现在服务端,因为服务端24*n运行
  • 一次性内存泄漏
    • 内存泄漏的代码在程序运行过程中只执行一次

导致内存泄漏的原因& 解决办法

总体来说 都属于资源未释放

  1. 长生命周期持有短声明周期的引用

    • 比如单例模式持有Activity的context
      • 解决办法
      • 将activity的context 换成ApplicationContext
  2. 非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类

    • 但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。

      • 解决办法

        • 将内部类定义成静态类即可

          • 如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;

            • new WeakReference<View>("Activity中的属性");
              
          • 在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;

  3. 连接资源没有被关闭,图片资源没有被释放,View 没有被销毁,属性动画没有close

    • 比如 数据库的Cousor ,Io 流 ,网络连接
      • 解决方案
        • 在不用时将资源调用close关闭即可 一般try catch finally 中的finally中关闭 此类连接
    • Bitmap 图片资源没有释放
      • 解决方案
        • 不用时调用recycle 释放即可
  4. 集合数据未及时清理导致的内存泄漏

    • 当集合数据过大导致内存泄漏
      • 解决方案
        • 当集合中数据不在使用,及时清空
  5. 注册API中的监听器,回调没有解除注册

    • 例如 动态注册广播,在activity退出时就要解除注册

      • 解决方案
        • 及时解除注册
    • 例如WebView sdk5.1会产生内存泄漏

      • 原因AwContents 类中注册了component callbacks,但是未正常反注册而导致的。导致WebView的ContentViewCore中的成员变量mContainerView引用着AccessibilityManagermAccessibilityStateChangeListeners导致activity不能被回收造成了泄漏。

      • 具体原因 mAccessibilityStateChangeListeners没移除mComponentCallbacks没有解除注册

        • onAttachedToWindow的时候调用mContext.registerComponentCallbacks(mComponentCallbacks)进行注册,
          onDetachedFromWindow中反注册

        • 但是在onDetachedFromWindow中有可能直接返回所以有可能不会执行反注册

        •     public void onDetachedFromWindow() {
                      if (isDestroyed()) return;
                      if (!mIsAttachedToWindow) {
                          Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
                          //直接返回
                          return;
                      }
                      mIsAttachedToWindow = false;
                      hideAutofillPopup();
                      nativeOnDetachedFromWindow(mNativeAwContents);
                      mContentViewCore.onDetachedFromWindow();
                      updateHardwareAcceleratedFeaturesToggle();
                      if (mComponentCallbacks != null) {
                          mContext.unregisterComponentCallbacks(mComponentCallbacks);
                          mComponentCallbacks = null;
                      }
                      mScrollAccessibilityHelper.removePostedCallbacks();
                      mNativeGLDelegate.detachGLFunctor();
                  }
          
        • 解决方案

          • 1 . 在销毁webview前一定要onDetachedFromWindow,我们先将webview从它的父view中移除再调用destroy方法,代码如下:

            @Override
            protected void onDestroy() {
               super.onDestroy();
               if (mWebView != null) {
                  ViewParent parent = mWebView.getParent();
                  if (parent != null) {
                     ((ViewGroup) parent).removeView(mWebView);
                  }
                  mWebView.removeAllViews();
                  mWebView.destroy();
                  mWebView = null;
               }
            }
            
            1. 利用framelauyout动态加载webview destory中先移除 在销毁 创建webview 用Application的context
            1. 单独开进程
            • activity中指定 进程名

            • 导致问题 application多次启动 可以获取当前进程名称判断是否需要初始化设置

  6. HandlerAsyncTask导致的内存泄漏

    • Handler 解决方案

      • 静态内部类 将Handler 定义为静态内部类

        • java总静态内部类不持有外部类引用
      • 弱引用 将引用的activity中的属性保存为弱引用 比如引用了Activity

        new WeakReference<Activity>(activity);
        
      • 移除所有信息调用removeCallbacksAndMessages

      • Handler产生原因

        • 当我们Activity销毁的时,有可能Handler还在继续等待执行未完成message,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即remove掉所有的消息和回调,以避免发生内存泄漏。
        • message 持有了handler对象
    • AsnycTask 解决方案

      • 将其定义成静态内部类
      • 引用activity内的属性 保存为弱引用 参考Handler
      • AsyncTask产生内存泄漏原因
        • 当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即cancelTimerTimerTask,以避免发生内存泄漏。
  7. String字符串拼接的问题

    • String 每一次拼接都会创建一个新的对象,造成内存占用过大
    • 解决方案
      • 使用StringBuffer
  8. 改变哈希值

    • 当对象被存储进HashSet集合后,就不能在对这个对象中参与计算哈希值的字段进行修改,一旦进行修改对象的哈希值会发生变化
      • 这种情况下,即使Contains方法使用当前对象的当前引用也不能检索到对象,会返回找不到该对象,也会导致该对象一直在Hashset中,无法删除,如果数据过多就会导致内存泄漏
  9. listview 构造Adapter时,没有使用缓存的 convertView

    • 解决方案 在实现adapter时 使用convertView缓存

总结

  • 应保持良好的编程习惯,根据需求及时的释放资源,根据需求选择适用的数据集合,比如android提供的SparseArrayArrayMap,比hashmap占用的内存更小

内存溢出 导致的原因

  1. 内存泄漏过多导致

  2. 系统分配内存过小

    • 可能还有其他的,欢迎留言

内存溢出解决方案

  • 保持良好的代码习惯,及时释放资源

  • 向系统申请更大的内存空间

    • AndroidManifest.xml中设置

    • android:largeHeap=”true” 为true 申请512M, false 申请192M
      
  • 使用更轻量级的数据结构

    • 使用轻量级的数据结构,替代hashmap,用sparseArray,Hashmap会导致一些没必要的自动装箱和拆箱。
  • 避免频繁的创建对象

    • 比如自定义view ondraw 就应该避免创建对象的操作
  • 资源文件图片与目录不匹配

    • ldpi QVGA (240×320)
    • mdpi HVGA (320×480)
    • hdpi WVGA (480×800)
    • FWVGA (480×854)
    • xhdpi 720P(1280x720)
    • xxhdpi 1080p(1920x1080 )
    • xxxhdpi 4K(3840×2160)