https://juejin.im/post/5c833e49e51d453a637c0af6

首先我们提出一个问题,什么是内存泄漏? 内存泄漏,通俗得来讲就是“没有用的对象无法被回收”
然后我们再提出一个问题,内存泄露会导致什么情况?
肯定是内存溢出,然后程序崩溃啊!

区别


相信初学者可能不太清楚内存溢出和内存泄漏的区别。

内存溢出:程序使用的空间大于原本系统给它申请的空间。

内存泄漏:在new了对象之后,没有使用这个对象了,但是又没有被回收,一直占用着内存。

储备知识

要想了解内存泄露的知识,首先我们要清楚以下的知识点

Java的GC(Garbage Collection,垃圾回收)机制。

Java的内存管理机制

详情可以见 浅谈垃圾回收算法


简单判断

如何判断,只用记住一点:A类实例引用B类实例,而A类实例的生命周期长于B类实例的生命周期。

泄漏场景

在Android开发中,内存泄漏的地方还是挺多的,有时候稍不注意就写出了一个内存泄漏的代码。所以说我们要熟记哪些地方容易发生内存泄漏,在代码Review的情况下很容易检查出来。

单例引起的内存泄漏

单例模式使用的地方非常多,它的生命周期常常伴随着App的一生,所以说也十分容易造成内存泄漏。

例如单例模式中引用Activity的Context,而单例模式的生命周期长于Activity。这里单例模式引用Activity的实例,当Activity被销毁,Activity无法被回收,造成内存泄露。

  1. public class Single {
  2. private static Single instance;
  3. private Context context;
  4. private Single(Context context) {
  5. this.context = context;
  6. }
  7. public static Single getInstance(Context context) {
  8. if (instance == null) {
  9. instance = new Single(context);
  10. }
  11. return instance;
  12. }
  13. }

值得一提的是,如果这里引用的Application的Context,将无任何影响。因为Application的生命周期与单例模式同样长。

集合的内存泄漏

在静态集合里面添加对象,添加完成之后该集合将会一直引用此对象,该对象无法被释放。(不过我们也写不出这样沙雕的代码来!

  1. static List<Object> objectList = new ArrayList<>();
  2. for (int i = 0; i < 10; i++) {
  3. Object obj = new Object();
  4. objectList.add(obj);
  5. obj = null;
  6. }

解决方法:在使用完该集合之后,将集合清空。


匿名内部类以及非静态内部类

特点:匿名类和非静态内部类都持有外部类的引用

匿名内部类


Handler泄漏

匿名内部类引起的内存泄露,最典型的例子就是Handler泄漏。当Handler的消息没有发送完毕,Activity就被销毁了,此时Activity无法被即时回收。

  1. public class MainActivity extends Activity{
  2. @SuppressLint("HandlerLeak")
  3. private Handler handler = new Handler() {
  4. @Override
  5. public void handleMessage(Message msg) {
  6. //do something...
  7. }
  8. };
  9. @Override
  10. public void onCreate(Bundle savedInstanceState){
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_main);
  13. }
  14. }

如何解决Handler泄漏呢?我们用static修饰Handler,这样Hanlder的生命周期就与Activity无关了。如果想引用Activity实例,这里可以用一个弱引用来获取。或者可以在Activity 的onDestroy() 方法中移除所有的消息

  1. handler.removeCallbacksAndMessages(null);
  2. public class MainActivity extends Activity{
  3. private final MyHandler handler = new MyHandler(this);
  4. @Override
  5. public void onCreate(Bundle savedInstanceState){
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8. }
  9. private static class MyHandler extends Handler {
  10. private final WeakReference<MainActivity> mActivity;
  11. public MyHandler(MainActivity activity) {
  12. mActivity = new WeakReference<MainActivity>(activity);
  13. }
  14. @Override
  15. public void handleMessage(Message msg) {
  16. MainActivity activity = mActivity.get();
  17. }
  18. }
  19. }



Thread泄漏

在Activity中new Thread时,如果在子线程做耗时操作,当Activity被销毁后,子线程的工作并未完成,此时会内存泄漏。

  1. public class MainActivity extends Activity{
  2. @Override
  3. public void onCreate(Bundle savedInstanceState){
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. try {
  10. Thread.sleep(100000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }).start();
  16. }
  17. }

这里同样可以继承 Thread 实现静态内部类来解决。

static修饰的成员变量

如果成员变量被声明为 static,其生命周期将与整个app进程生命周期一样。

Stream未关闭

在调用了流之后,一定要记得关闭流。用到流的地方一般都是文件操作,虚拟机无法通过垃圾回收来释放这些资源。

其他泄漏

例如service忘记解除绑定,broadcastReceiver忘记解除订阅,EventBus忘记解除订阅等。

常用的检测内存泄漏的工具

光凭肉眼我们其实只能找出比较明显的内存泄露点,还有许多隐藏得比较深的内存泄露。那么我们如何找到这些点呢?当然是利用工具。

Android Lint:Android Studio提供的代码扫描分析工具

Leakcanary: Square 公司开源的「Android 和 Java 的内存泄漏检测库」

总结

在Android系统中,每个App最多能分配大约只有100-200MB的内存空间,因为内存不够,溢出而引起的程序崩溃还是不在少数。所以说,日常开发中还是要千万注意内存泄露。