Java Thread 是怎么造成内存泄露的?

问:简单解释下 Thread 是如何造成内存泄漏的?

答:解释这个问题需要举几个例子。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread() {
            @Override
            public void run() {
                while(true) {
                    SystemClock.sleep(10);
                }
            }
        }.start();
    }
}

上面例子中的匿名内部类 Thread 实例会长久运行,不会被系统 GC 回收。Threads 在 Java 中是 GC roots,意味着 Dalvik Virtual Machine (DVM) 会为所有活跃的 threads 在运行时系统中保持一个硬引用,这会导致 threads 一直处于运行状态,垃圾收集器将永远不可能回收它。

其次,非静态内部类会持有外部类的引用。Thread 会长久地持有 Activity 的引用,使得系统无法回收 Activity 和它所关联的资源和视图。

针对后者,MainActivity 的内存泄漏问题,只需把非静态的 Thread 匿名类定义成静态的内部类就行了。

public class MainActivity extends Activity {
  @Override  
  protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    exampleTwo();  
  }  

  private void exampleTwo() {  
    new MyThread().start();  
  }  

  private static class MyThread extends Thread {  
    @Override  
    public void run() {  
      while(true) {  
        SystemClock.sleep(1000);  
      }  
    }  
  }  
}

现在新创建的 Thread 不会持有 MainActivity 的一个隐式引用,也就不会阻止垃圾回收器对 MainActivity 的回收工作了。

而在第一个问题中,一旦有一个新的 Activity 创建,那么就有一个 Thread 永远得不到清理回收,发生内存泄漏。

出于这个原因,我们应当为 thread 添加一个结束的逻辑,如下代码所示:

public class MainActivity extends Activity {  
  private MyThread mThread;  

  @Override  
  protectedvoid onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    exampleThree();  
  }  

  privatevoid exampleThree() {  
    mThread = new MyThread();  
    mThread.start();  
  }  

  /** 
   * Static inner classes don't hold implicit references to their 
   * enclosing class, so the Activity instance won't be leaked across 
   * configuration changes. 
   */  
  private static class MyThread extends Thread {  
    private boolean mRunning = false;  

    @Override  
    public void run() {  
      mRunning = true;  
      while(mRunning) {  
        SystemClock.sleep(1000);  
      }  
    }  

    public void close() {  
      mRunning = false;  
    }  
  }  

  @Override  
  protectedvoid onDestroy() {  
    super.onDestroy();  
    mThread.close();  
  }  
}

在上述的代码中,当 Activity 结束销毁时在 onDestroy() 方法中结束了新创建的线程,保证了 thread 不会发生泄漏。但是如果你想在手机配置发生改变时,保持原有的线程而不重新创建的话,你可以考虑使用 fragment 来保留原有的线程,以备下一次使用。

因此得出如下结论:

  1. 使用静态内部类/匿名类,不要使用非静态内部类/匿名类。非静态内部类/匿名类会隐式地持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式地持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显式地持有一个弱引用。

  2. 不要以为 Java 永远会帮你清理回收正在运行的 threads。在上面的代码中,我们很容易误以为当 Activity 结束销毁时会帮我们把正在运行的 thread 也结束回收掉,但事情永远不是这样的!Java threads 会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为 thread 设置退出逻辑条件的习惯。

作者:方衍
链接:https://www.jianshu.com/p/f50366145b4b
來源:简书

本文引用自 Thread 是怎么造成内存泄露的?

;