当我在Thread对象上调用run()时,为什么Java程序会泄漏内存?

时间:2020-03-06 14:28:53  来源:igfitidea点击:

(Jeopardy风格的问题,希望当我遇到此问题时,答案已经在线)

使用Java 1.4,我有一种方法希望在某些时候作为线程运行,而在其他时候则不想。因此,我将其声明为Thread的子类,然后根据需要将其称为start()或者run()。

但是我发现我的程序会随着时间的流逝而泄漏内存。我究竟做错了什么?

解决方案

这是Java 1.4中的一个已知错误:
http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=5869e03fee226ffffffffc40d4fa881a86e3:WuuT?bug_id=4533087

它在Java 1.5中已修复,但Sun并不打算在1.4中对其进行修复。

问题是,在构造时,将"线程"添加到内部线程表中的引用列表中。直到它的start()方法完成后,它才会从该列表中删除。只要存在该引用,就不会收集垃圾。

因此,除非我们肯定要调用它的start()方法,否则请不要创建线程。线程对象的run()方法不应直接调用。

一种更好的编码方法是实现Runnable接口,而不是子类Thread。当我们不需要线程时,请致电

myRunnable.run();

当我们确实需要线程时:

Thread myThread = new Thread(myRunnable);
myThread.start();

让我们看看是否可以更进一步地解决问题的核心:

如果在线程中使用start()启动程序(假设)为1000 x,然后在线程中使用run()启动程序为1000 x,这两个内存是否都松散了?如果是这样,则应检查算法(即是否有外部对象,例如Runnable中使用的Vector)。

如果没有如上所述的此类内存泄漏,则应调查有关JVM的线程的启动参数和内存使用情况。

我怀疑构造Thread实例或者其子类会泄漏内存。首先,在Javadocs或者Java语言规范中没有提到任何种类的东西。其次,我运行了一个简单的测试,它还显示没有内存泄漏(至少不是在32位x86 Linux 2.6上的Sun的JDK 1.5.0_05上泄漏):

public final class Test {
  public static final void main(String[] params) throws Exception {
    final Runtime rt = Runtime.getRuntime();
    long i = 0;
    while(true) {
      new MyThread().run();
      i++;
      if ((i % 100) == 0) {
        System.out.println((i / 100) + ": " + (rt.freeMemory() / 1024 / 1024) + " " + (rt.totalMemory() / 1024 / 1024));
      }
    }
  }

  static class MyThread extends Thread {
    private final byte[] tmp = new byte[10 * 1024 * 1024];

    public void run() {
      System.out.print(".");
    }
  }
}

编辑:只是总结以上测试的想法。线程的MyThread子类的每个实例都引用其自己的10 MB数组。如果未对MyThread的实例进行垃圾回收,则JVM将很快耗尽内存。但是,运行测试代码表明,无论到目前为止构建的MyThreads数量如何,JVM都使用少量恒定的内存。我声称这是因为MyThread的实例被垃圾收集了。