Почему моя Java-программа утечки памяти, когда я вызываю run() для объекта Thread?

(вопрос в стиле, связанный со здоровьем, я хочу, чтобы ответ был в сети, когда у меня была эта проблема)

Используя Java 1.4, у меня есть метод, который я хочу запускать как поток некоторое время, но не в других. Поэтому я объявил его подклассом Thread, затем либо вызвал start(), либо run() в зависимости от того, что мне нужно.

Но я обнаружил, что моя программа будет течь с течением времени. Что я делаю неправильно?

Ответ 1

Это известная ошибка в Java 1.4: http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=5869e03fee226ffffffffc40d4fa881a86e3:WuuT?bug_id=4533087

Исправлено в Java 1.5, но Sun не намерен исправлять его в версии 1.4.

Проблема заключается в том, что во время построения a Thread добавляется в список ссылок во внутренней таблице потоков. Он не будет удален из этого списка до завершения метода start(). Пока эта ссылка есть, она не получит сбор мусора.

Итак, никогда не создавайте поток, если вы определенно не назовете его метод start(). Метод Thread object run() не должен вызываться напрямую.

Лучшим способом кодирования является реализация интерфейса Runnable, а не подкласса Thread. Когда вам не нужен поток, вызовите

myRunnable.run();

Когда вам нужен поток:

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

Ответ 2

Я сомневаюсь, что построение экземпляра Thread или его подкласса утечки памяти. Во-первых, ничего подобного не упоминается в Javadocs или Java Language Specification. Во-вторых, я проверил простой тест, и он также показывает, что утечка памяти (по крайней мере, не на Sun JDK 1.5.0_05 на 32-разрядной версии x86 Linux 2.6):

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(".");
    }
  }
}

EDIT: просто подытожим идею теста выше. Каждый экземпляр подкласса MyThread Thread ссылается на собственный массив размером 10 МБ. Если экземпляры MyThread не были собраны в мусор, у JVM довольно быстро закончилась память. Однако запуск тестового кода показывает, что JVM использует небольшой постоянный объем памяти независимо от количества созданных MyThreads. Я утверждаю, что это связано с тем, что экземпляры MyThread собираются с мусором.

Ответ 3

Посмотрим, можем ли мы приблизиться к сути проблемы:

Если вы запустите свою программу (скажем) 1000 x, используя start(), то 1000 x используя run() в потоке, выполните обе освобожденную память? Если это так, то ваш алгоритм должен быть проверен (т.е. Для внешних объектов, таких как векторы, используемые в вашем Runnable).

Если утечка памяти отсутствует, как описано выше, вы должны исследовать начальные параметры и использование памяти потоков в JVM.