Why Does Executors.newSingleThreadExecutor Shut Down Unexpectedly? Uncovering GC and Finalize Mysteries

This article investigates the intermittent RejectedExecutionException caused by Executors.newSingleThreadExecutor, explains how Java's garbage collector and finalize method can prematurely shut down thread pools, demonstrates the behavior with reproducible code, and shows how newer JDK versions have fixed the issue.

Programmer DD
Programmer DD
Programmer DD
Why Does Executors.newSingleThreadExecutor Shut Down Unexpectedly? Uncovering GC and Finalize Mysteries

Problem Description

While troubleshooting an occasional thread‑pool error in production, the following exception was observed:

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@a5acd19 rejected from java.util.concurrent.ThreadPoolExecutor@30890a38[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

The reproduced code creates a single‑thread executor via Executors.newSingleThreadExecutor() and submits a task that returns a Future result.

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 8; i++) {
            new Thread(() -> {
                while (true) {
                    Future<String> future = threadPoolTest.submit();
                    try {
                        String s = future.get();
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        // Simulate GC pressure
        new Thread(() -> {
            while (true) {
                System.gc();
            }
        }).start();
    }

    /** Asynchronous task */
    public Future<String> submit() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            Thread.sleep(50);
            return System.currentTimeMillis() + "";
        });
        executorService.execute(futureTask);
        return futureTask;
    }
}

Analysis & Questions

The first question is why the thread pool is shut down even though the code never calls shutdown(). The source of Executors.newSingleThreadExecutor creates a FinalizableDelegatedExecutorService, whose finalize() method invokes shutdown() before the object is garbage‑collected.

GC only reclaims unreachable objects, but the JIT compiler may nullify variables that are no longer used, making them unreachable earlier than the stack frame ends. This can cause finalize() to run while the method is still active.

A reachable object is any object that can be accessed in any potential continuing computation from any live thread. Optimizing transformations may set a variable to null earlier, allowing the object to become reclaimable sooner.

Experiments show that if an object is set to null or a thread switch occurs, the object can be finalized early. Adding a final reference (e.g., printing the object) prevents early finalization.

Conclusion

Even though GC only collects unreachable objects, compiler/JIT optimizations can make objects unreachable earlier, causing premature finalization. Relying on finalize() for important cleanup (as in Executors.newSingleThreadExecutor) is therefore unsafe and was fixed in JDK 11 by adding a reachability fence.

References

https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope

https://bugs.openjdk.java.net/browse/JDK-8145304

https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.6.1

https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.3

https://stackoverflow.com/questions/58714980/rejectedexecutionexception-inside-single-executor-service

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyThreadPoolGarbageCollectionfinalize
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.