Почему этот пул потоков не собирает мусор? - PullRequest
30 голосов
/ 11 октября 2011

В этом примере кода используется ExecutorService, которому разрешено выходить за рамки.

public static void main(String[] args)
{
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    executorService.submit(new Runnable()
    {
        public void run()
        {
            System.out.println("hello");
        }
    });
}

Как только executorService выходит за пределы области видимости, его следует собрать и завершить.Метод finalize () в ThreadPoolExecutor вызывает shutdown ().

/**
 * Invokes {@code shutdown} when this executor is no longer
 * referenced and it has no threads.
 */
protected void finalize() {
    shutdown();
}

После вызова shutdown () потоки пула должны завершиться, и JVM должно быть разрешено выйти.Однако executorSerivce никогда не собирается и, следовательно, JVM остается в живых.Кажется, что даже вызовы System.gc () не работают.Почему executorService не собирается даже после завершения main ()?

Примечание: я знаю, что должен сам вызывать shutdown (), и я всегда делаю это вне тестирования.Мне любопытно, почему завершение не работает как резервная копия.

Ответы [ 5 ]

49 голосов
/ 11 октября 2011

Это на самом деле не имеет ничего общего с тем, что GC является недетерминированным, хотя это не помогает!(Это одна из причин в вашем примере, но даже если мы «исправим» ее, чтобы она использовала память и принудительно собрала коллекцию, она все равно не была бы завершена)

Рабочие потоки, которые создает исполнитель, являются внутренними классами, которыеиметь ссылку обратно на самого исполнителя.(Им нужно, чтобы он мог видеть очередь, состояние выполнения и т. Д.!). Работающие потоки не собираются сборщиком мусора, поэтому с каждым потоком в пуле, имеющим эту ссылку, они будут поддерживать исполнителя, пока все потоки не будут мертвы.Если вы не сделаете что-то вручную, чтобы остановить потоки, они будут работать вечно, и ваша JVM никогда не будет закрыта.

20 голосов
/ 01 мая 2012

Affe правильно;потоки пула потоков не позволят собрать мусор.Когда вы вызываете Executors.newFixedThreadPool (3), вы получаете ThreadPoolExecutor, сконструированный так:

ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

И если вы читаете JavaDoc для ThreadPoolExecutor, он говорит:

Пул, который являетсябольше не упоминается в программе, а остальные потоки не будут автоматически отключены.Если вы хотите, чтобы неиспользуемые пулы были возвращены, даже если пользователи забыли вызвать shutdown (), тогда вы должны сделать так, чтобы неиспользованные потоки в конечном итоге умерли, , установив соответствующее время поддержания активности, используя нижнюю границупотоки с нулевым ядром и / или настройка allowCoreThreadTimeOut (boolean).

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

0 голосов
/ 11 октября 2011

Финализаторы слишком непредсказуемы. В зависимости от них это обычно плохая практика. Вы можете прочитать больше об этом в "Effective java " Джошуа Блоха (позиция 1.7)

0 голосов
/ 11 октября 2011

Как только executorService выходит из области видимости, он должен быть собран и завершен.

Не совсем - как только он выходит из области видимости, он может получить изавершена.В спецификации VM нет никаких гарантий относительно того, когда объекты завершены, или даже , если они завершены:

Язык программирования Java не определяеткак скоро будет вызван финализатор, кроме как сказать, что это произойдет до повторного использования хранилища для объекта.

0 голосов
/ 11 октября 2011

Поскольку сборка мусора является «недетерминированной», т.е. вы не можете предсказать, когда это произойдет, поэтому вы не можете точно предсказать, когда будет запущен метод finalize.Вы можете только сделать Объекты подходящими для GC и предложить gc с System.gc () без каких-либо гарантий.

Еще худшие потоки зависят от ОС и обрабатываются JVM и едва ли предсказуемы ...

...