GC thinking caused by a thread pool bug!

Time:2020-5-15

Author: empty
https://segmentfault.com/a/1190000021109130

Problem description

A few days ago, I was helping my colleagues to troubleshoot the occasional thread pool errors on the production line. The logic was very simple. The thread pool performed an asynchronous task with results.

However, there have been occasional errors recently:

java.util.concurrent.RejectedExecutionException: Task [email protected] rejected from java.util.concurrent.Thread[email protected]\[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0\]

The simulation code in this article has been in the hotspot java8 (1.8.0) version of the simulation & appeared

The following is the simulation code. Create a single thread pool through executors.newsingthreadexector, and then get the result of future at the caller

public class ThreadPoolTest {    public static void main(String\[\] args) {        final ThreadPoolTest threadPoolTest = new ThreadPoolTest();        for (int i = 0; i  future = threadPoolTest.submit();                        try {
                            String s = future.get();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        } catch (Error e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }        
        //Sub thread keeps GC, simulating sporadic GC
        new Thread(new Runnable() {            @Override
            public void run() {                while (true) {
                    System.gc();
                }
            }
        }).start();
    }    /**
     *Asynchronous task execution
     * @return
     */
    Public future submit() {// the key point is to create a single thread pool through executors.newsingthreadexector
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        FutureTask futureTask = new FutureTask(new Callable() {            @Override
            public Object call() throws Exception {
                Thread.sleep(50);                return System.currentTimeMillis() + "";
            }
        });
        executorService.execute(futureTask);        return futureTask;
    }

}

Analysis & questions

The first question is: why the thread pool is closed? There is no place in the code to close it manually. to glance atExecutors.newSingleThreadExecotorSource code implementation of:

public static ExecutorService newSingleThreadExecutor() {    return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,                    0L, TimeUnit.MILLISECONDS,                    new LinkedBlockingQueue()));
}

What is created here is actually aFinalizableDelegatedExecutorService, this wrapper class overridesfinalizeFunction, that is to say, this class will execute the shutdown method of thread pool before being recycled by GC.

The problem is that GC will only recycle unreachable objectssubmitBefore the stack frame of the function is completed and the stack is released,executorServiceIt should be accessible.

For this problem, first throw a conclusion:

When the object still exists in the stack frame,finalizeMay also be implemented

There is an introduction to finalize in the Oracle JDK document:

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.

The reachable object is any object that can be accessed continuously from any active thread. The java compiler or code generator may set the object that is no longer accessed to null in advance, so that the object can be recycled in advance

That is to say, under the optimization of JVM, there may be an empty and recycling situation after the object is not reachable. Pay attention to WeChat official account: Java technology stack, reply in the background: Java, get the latest Java tutorial I have finished N, all dry cargo.

Take an example to verify (from https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope):

class A {
    @Override protected void finalize() {
        System.out.println(this + " was finalized!");
    }    public static void main(String\[\] args) throws InterruptedException {
        A a = new A();
        System.out.println("Created " + a);        for (int i = 0; i

As can be seen from the example, if a is no longer used after the completion of the loop, it will execute finalize first. Although from the object scope, the method is not finished and the stack frame is not out of the stack, it will be executed in advance.

Now add a line of code to print object a on the last line, and let the compiler / code generator think that there is a reference to object a after it

... system. Out. Println (a); // print the result created a @ 1be6f5c3
done.
[email protected]

As a result, the finalize method is not executed (because the main method finishes execution and the process ends directly), let alone the problem of finalize in advance

Based on the above test results, another test is to set the object a to null before the loop, and print the reference to keep the object a at last

A a = new A();System.out.println("Created " + a);
A = null; // manually set nullfor (int i = 0; I

As a result, if you manually set null, the object will be recycled in advance. Although there is a reference at the end, it is also null


Now go back to the above thread pool problem. According to the mechanism described above, after analyzing no reference, the object will be finalized in advance

In the above code, it is clear that there is a reference before returnexecutorService.execute(futureTask), why is it also finalized in advance?

It is speculated that in the execute method, the ThreadPoolExecutor will be called, and a new thread will be created and started. At this time, an active thread switch will occur, resulting in the unreachable objects in the active thread

Combined with the description in the Oracle JDK document above that “reachable object is any object that can be accessed from any potential continuous access of any active thread”, it can be considered that the object is considered unreachable due to a displayed thread switch, leading to the finalization of the thread pool in advance

Let’s test our conjecture:

//The entry function public class finalizedtest {public static void main (string \ [\] args) {final finalizedtest finalizedtest = new finalizedtest(); for (int i = 0; I

Error is reported after several times of execution:

Exception in thread "Thread-1" java.lang.RuntimeException: reject!!!\[true\]

From the error point of view, “thread pool” is also shut down in advance, so it must be caused by new threads?

Next, change the new thread toThread.sleepTest it:

//Tthreadpoolexecutor.java, the modified execute method public void execute() {try {// explicit sleep 1 ns, to actively switch threads
        TimeUnit.NANOSECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }// simulate the ThreadPoolExecutor, start the new thread, cycle to check the thread pool status, and verify whether it will shut down in finalize
    //If the thread pool is shut down in advance, an exception is thrown
    for (int i = 0; i

The execution result is the same as error reporting

Exception in thread "Thread-3" java.lang.RuntimeException: reject!!!\[true\]

Thus, if an explicit thread switch occurs during execution, the compiler / code generator will think that the outer wrapper object is not reachable

summary

Although GC can only recycle the objects that can’t reach GC root, under the optimization of compiler (not explicitly pointed out, it may also be JIT) / code generator, there may be the situation that the object is set to null in advance, or the “advanced object can’t reach” caused by thread switching.

So if you want to do something in the finalize method, you must refer to the object in the last display (toString / hashcode is OK), and keep the reachable of the object

As for the above, the object caused by thread switching is not reachable, and there is no support from official literature. It is just a test result of an individual. If there is any problem, please point out

To sum up, this recycling mechanism is not a bug of JDK, but an optimization strategy, just recycling in advance; butExecutors.newSingleThreadExecutorThere is a bug in the implementation of finalize to automatically close the thread pool. After optimization, it may cause the thread pool to shut down in advance, resulting in exceptions.

This problem of thread pool is also an open but unresolved problem in the JDK forum https://bugs.openjdk.java.net/browse/jdk-8145304.

However, under jdk11, the problem has been fixed:

JUC  Executors.FinalizableDelegatedExecutorServicepublic void execute(Runnable command) {    try {
        e.execute(command);
    } finally { reachabilityFence(this); }
}

WeChat official account: Java technology stack, back in the background: Java, can get the latest Java tutorial I have compiled N, all dry cargo.

Recommend to my blog to read more:

1.Java JVM, collection, multithreading, new features series tutorial

2. Spring MVC, spring boot, spring cloud series tutorials

3. Maven, GIT, eclipse, IntelliJ idea series tools tutorial

4.Java , back end, architecture, Alibaba and other big factories’ latest interview questions

Life is beautiful. See you tomorrow

Recommended Today

nrm — NPM registry manager

nrm — NPM registry manager nrm can help you easy and fast switch between different npm registries, now include: npm, cnpm, taobao, nj(nodejitsu). nrmCan help you quickly and easily in different NPMregistriesSwitch, including the followingregistries, NPM official (foreign), cnpm (domestic), Taobao (Taobao mirror domestic),nj(nodejitsu) Domestic users can use Taobao to greatly improve the download speed […]