Why doesn't this thread pool get garbage colle

2020-02-07 21:27发布

问题:

In this code example, the ExecutorService is used one and allowed to go out of scope.

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

Once executorService is out of scope, it should get collected and finalized. The finalize() method in ThreadPoolExecutor calls shutdown().

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

Once shutdown() is called, the pool threads should terminate and the JVM should be allowed to exit. However the executorSerivce is never getting collected and thus the JVM stays alive. Even calls to System.gc() don't seem to work. Why isn't executorService getting collected even after main() terminates?

Note: I know I should call shutdown() myself and I always do outside of testing. I'm curious why finalization isn't working as a back-up here.

回答1:

This doesn't really have anything to do with GC being non-deterministic, although it doesn't help! (That is one cause in your example, but even if we 'fixed' it to eat up memory and force a collection, it still wouldn't finalize)

The Worker threads that the executor creates are inner classes that have a reference back to the executor itself. (They need it to be able to see the queue, runstate, etc!) Running threads are not garbage collected, so with each Thread in the pool having that reference, they will keep the executor alive until all threads are dead. If you don't manually do something to stop the threads, they will keep running forever and your JVM will never shut down.



回答2:

Affe is correct; the thread pool's threads will keep it from being garbage collected. When you call Executors.newFixedThreadPool(3) you get a ThreadPoolExecutor constructed like so:

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

And if you read the JavaDoc for ThreadPoolExecutor it says:

A pool that is no longer referenced in a program AND has no remaining threads will be shutdown automatically. If you would like to ensure that unreferenced pools are reclaimed even if users forget to call shutdown(), then you must arrange that unused threads eventually die, by setting appropriate keep-alive times, using a lower bound of zero core threads and/or setting allowCoreThreadTimeOut(boolean).

If you want your thread pool to finalize like you're expecting, you should do one of those things.



回答3:

Because garbage collection is “non deterministic” ie you cannot predict when it will happen, you thus cannot predict exactly when the finalize method will run. You can only make Objects eligible for GC and suggest gc with System.gc() without any guarantee.

Even worse threads are OS specific handled by the JVM and are hardly predictable...



回答4:

Finalizers are too unpredictable. Depending on them is usually bad practice. You can read more about it in "Effective java" by Joshua Bloch (item 1.7)



回答5:

Once executorService is out of scope, it should get collected and finalized.

Not really - once it is out of scope, it could get collected and finalized. There are no guarantees made in the VM spec about when objects are finalized, or even if they are finalized:

The Java programming language does not specify how soon a finalizer will be invoked, except to say that it will happen before the storage for the object is reused.