Подтвердить что ты не робот

Почему этот пул потоков не получает сбор мусора?

В этом примере кода 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 должно быть разрешено выйти. Однако исполнитель никогда не собирается собираться, и, таким образом, JVM остается в живых. Даже вызовы System.gc() не работают. Почему не выполняется сбор исполнительной службы даже после завершения main()?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Даже худшие потоки - это ОС, которые обрабатываются JVM и вряд ли предсказуемы...

Ответ 4

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

Ответ 5

Как только функция executorService выходит за пределы области действия, она должна быть собрана и завершена.

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

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