Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JVM not exiting until daemon thread not completed

I just stumbled upon a weird behavior of daemon threads which I can't explain. I've reduced my code to a minimal, complete and verifiable sample:

public static void main(String[] args) throws InterruptedException {

    Thread runner = new Thread(() -> {

        final int SIZE = 350_000;
        for (int i = 0; i < SIZE; i++) {
            for (int j = i + 1; j < SIZE; j++) {
                if (i*j == SIZE * SIZE - 1) {
                    return;
                }
            }
        }
    });

    runner.setDaemon(true);
    runner.start();

    // Thread.sleep(1000);
    System.out.println("Exiting.");
}

The code executed by the runner thread takes about 12 secs to terminate on my box, and we're not interested in what it does, since I just needed to spend some time computing.

If this snippet is run as it is, it works as expected: it terminates just after its start. If I uncomment the Thread.sleep(1000) line and run the program, it works for about 12 seconds, then prints out "Exiting" and terminates.

As far as I understood how daemon threads work, I expected this code to run for 1 second and then to terminate execution, since the only user thread running is the one launched with the main() method (the runner is a background daemon thread) and as soon as the 1000 msec are passed, it reaches the end of its execution and the JVM should stop. Also, it looks quite strange that "Exiting" is printed only after 12 seconds, and not when the program starts.

Am I wrong? How can I achieve the desired behavior (pause for a second and then stop, independently from what the runner thread is doing)?

I'm using a 64bit Oracle JDK 1.8.0_112 on a linux box and it has the same behavior either if launched from an IDE or from the command line.

Thanks, Andrea

like image 601
Andrea Iacono Avatar asked Jan 03 '17 14:01

Andrea Iacono


2 Answers

This is maybe a consequence of counted loop optimization which removed safepoint polls from your nested loops. Try to add -XX:+UseCountedLoopSafepoint flag to your JVM startup options.

like image 91
Alexander Yanyshin Avatar answered Sep 27 '22 17:09

Alexander Yanyshin


Thread#sleep(long) pauses the main thread before it returns from its main method (i.e. before the JVM is considering the program done as long as no non-deamon threads are alive). The scheduler is then free to run any other runnable thread which would be the deamon thread. As it stands, there is no apparent reason for the JVM to forcibly preempt the deamon thread before it finishes execution to continue in the main thread (is it's done sleeping yet), so the JVM is free to continue its schedule. However, it may at any time elect to pause the running thread and schedule another runnable thread for execution, so reproducibility is not guaranteed for your example.

You can force a preemption by inserting calls to Thread#yield() or #sleep(1) in the loops. I bet you'll start seeing the snippet exiting faster and before it finishes the loops.

There's more to learn about Thread states and scheduling, a nice overview can be found here.

Update for comment:

I cannot modify the code in the background thread (is a requirement), so I was looking for a way to stop it if it takes too long (a description of what I'm doing is stackoverflow.com/questions/41226054/… ).

It's legally only possible to stop a running thread from within, so you usually have it test an abort condition every iteration, and if the condition is met, the run method return;s. An abort condition could be as simple as a boolean flag that is set from the outside (! volatile caveat !). So the dead-simplest solution would be to have the main thread set such a flag after the sleep.

Another possibility might be using an ExecutorService that supports timeouts, see this Q&A for an example involving ScheduledExecutorService.

Still I don't understand how the scheduler can decide to wait for 12 seconds before running the System.out instruction.

It does not wait 12 seconds, it let's the deamon thread run to completion because being a deamon only matters to the JVM when deciding if it's safe to halt the JVM. For the scheduler, only the state of the thread matters and as far as it's concernced, after the 1s sleep of the main thread, it has a running (deamon) and a runnable thread (main), and no indication that the running thread should be paused in favor for the runnable thread. Switching threads is also expensive computationally, so the scheduler might be reluctant lacking any indication. An indication to switch might be sleeps and yields, but also GC runs and a whole lot of other things.

like image 45
hiergiltdiestfu Avatar answered Sep 27 '22 18:09

hiergiltdiestfu