Android 内存泄漏总结以及优化

861 阅读3分钟

定义

运行时数据区域
从虚拟机运行时数据区域看到,gc 管理的主要区域是 Java 堆,一般情况下只针对堆进行垃圾回收。而方法区、本地方法栈和虚假机栈不被 gc 所管理,因而选择这些区域内的对象作为 GC roots,不被GC回收,即 GC roots 可达,不可被回收

内存泄漏监控

一般使用LeakCanary进行内存泄漏的监控即可

使用方法参考 leakcanary使用

常见的内存泄漏场景

1. 资源对象未关闭

资源性对象如File、Cursor、Stream、Socket,Bitmap,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患

2. 注册对象没有注销(Listener 没有及时 unRegister)

我们常常写很多的Listener,没有注销会导致观察者列表里维持着对象的引用,阻止 gc 回收

3. 类的静态变量持有大数据对象.(有什么例子呢??)

静态变量长期维持到大数据对象的引用,阻止 gc 回收

4. 非静态内部类的静态实例

由于非静态内部类默认持有外部类的引用,而静态实例属于类对象。所以当外部类被销毁时,内部类仍然持有外部类的引用,导致外部类无法被 gc 回收

解决方法:可以选择使用静态内部类,如需要持有外部类,可以使用弱引用来引用外部类


public class OutClass {
   int age = 10;
   public static innerClass aATest; // 内部类静态实例
    static class innerClass {  // 静态内部类
        WeakReference<OutClass> mOutClass;
        public innerClass(OutClass outClass) {
        mOutClass = new WeakReference<>(outClass); // 弱引用持有外部类
        }
     public void test() {
       int age = mOutClass.get().age; // 使用外部类变量
     }
  }
}

5. Handler 临时性内存泄漏

匿名内部类也是默认持有外部类的引用。

handler 发出 message 后存储在 MessageQueue 中,在 message 中存在一个 target,它是 Hanlder 的一个引用。

如果 Hanlder 是非静态的,而当 Hanlder 所在的对象退出时, message 还是在 MessageQueue 中,则 message 持有 Handler 对象,而 Handler 又持有所在对象的引用,所以导致所在对象无法被 gc 回收

解决方法:静态内部类并继承Handler,如需要持有外部类,可以使用弱引用来引用外部类

public class MainActivity extends BaseActivity {

  MyHandler myHandler = new MyHandler(this);
//  Handler mHandler = new Handler() {
//    @Override
//    public void handleMessage(Message msg) {
//      super.handleMessage(msg);
//    }
//  };


  /**
   * 创建静态内部类
   */
  private static class MyHandler extends Handler {
    //持有弱引用HandlerActivity,GC回收时会被回收掉.
    private final WeakReference<MainActivity> mActivty;

    public MyHandler(MainActivity activity) {
      mActivty = new WeakReference<MainActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = mActivty.get();
      super.handleMessage(msg);
      if (activity != null) {
        //执行业务逻辑
      }
    }
  }

  private static final Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
      //执行我们的业务逻辑
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //解决了内存泄漏,延迟5分钟后发送
    myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
  }

6. WebView

WebView 都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉

解决方法:为WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

7. Context 泄露

创建单例对象、静态方法或其他的时候,根据不同 context的生命周期合理使用