ADDITIONAL SYSTEM INFORMATION :
openjdk version "16" 2021-03-16
OpenJDK Runtime Environment (build 16+35-2229)
OpenJDK 64-Bit Server VM (build 16+35-2229, mixed mode, sharing)
Mac OS X Catalina
A DESCRIPTION OF THE PROBLEM :
On OS X, it appears that some ZWorker threads continue running even after System.exit() or kill or CTRL+C have been used to kill the process. With this experiment it can continue for up to a minute.
In this experiment, we create thousands of linked lists in hundreds of threads. It is a nasty dataset, and probably not what would be encountered "in the wild". However, all the other GC algorithms shut down immediately, such as SerialGC, Throughput GC, CMS, G1, Shenandoah and even Epsilon.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please run the attached code with the following JVM parameters:
-Xmx50g -Xms50g -XX:+UseZGC
After one minute, the timer thread calls System.exit(). The shutdown hook thread is immediately called and we would expect the JVM to shut down within about a second.
We only managed to produce this bug on OS X, not on Linux.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected result would be for the JVM to shut down after about a minute. Once we see the output
Shut down initiated - total time = 60004ms
Then the JVM should shut down almost immediately.
ACTUAL -
The JVM continues running after the line
Shut down initiated - total time = 60004ms
The main thread still runs, as well as the threads that we started in the test. In the
---------- BEGIN SOURCE ----------
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.stream.*;
/**
* Run with java -Xmx50g -Xms50g -XX:+UseZGC.
* <p>
* Once the Shut down is initiated, the JVM does not immediately
* stop, but the ZWorker threads are still running doing marking.
*
* @author Heinz M. Kabutz
*/
public class StrainingZGC {
private static final LongAdder total = new LongAdder();
private static final int SIZE = 20000;
private static final int THREADS = 200;
public static void main(String... args) throws InterruptedException {
Timer timer = new Timer(true);
timer.schedule(new TimerTask() {
public void run() {
System.exit(0);
}
}, 60_000);
long start = System.nanoTime();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.printf("Shut down initiated - total time = %dms%n",
((System.nanoTime() - start) / 1_000_000));
}));
for (int i = 0; i < THREADS; i++) {
int from = i * (SIZE / THREADS);
int to = Math.min(from + (SIZE / THREADS) * 10, SIZE);
Thread thread = new Thread(() -> {
List<List<Integer>> listOfLists = new ArrayList<>();
for (int j = 0; j < SIZE / THREADS; j++) {
listOfLists.add(
IntStream.range(0, SIZE)
.boxed()
.collect(Collectors.toCollection(LinkedList::new)));
}
while (true) {
total.add(listOfLists.stream().mapToLong(
list -> list.stream().mapToLong(Integer::longValue).sum()
).sum());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new CancellationException("interrupted");
}
}
});
thread.start();
}
while (true) {
System.out.println("total = " + total);
Thread.sleep(5000);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Don't create such a ridiculous object graph :)
FREQUENCY : always
openjdk version "16" 2021-03-16
OpenJDK Runtime Environment (build 16+35-2229)
OpenJDK 64-Bit Server VM (build 16+35-2229, mixed mode, sharing)
Mac OS X Catalina
A DESCRIPTION OF THE PROBLEM :
On OS X, it appears that some ZWorker threads continue running even after System.exit() or kill or CTRL+C have been used to kill the process. With this experiment it can continue for up to a minute.
In this experiment, we create thousands of linked lists in hundreds of threads. It is a nasty dataset, and probably not what would be encountered "in the wild". However, all the other GC algorithms shut down immediately, such as SerialGC, Throughput GC, CMS, G1, Shenandoah and even Epsilon.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please run the attached code with the following JVM parameters:
-Xmx50g -Xms50g -XX:+UseZGC
After one minute, the timer thread calls System.exit(). The shutdown hook thread is immediately called and we would expect the JVM to shut down within about a second.
We only managed to produce this bug on OS X, not on Linux.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected result would be for the JVM to shut down after about a minute. Once we see the output
Shut down initiated - total time = 60004ms
Then the JVM should shut down almost immediately.
ACTUAL -
The JVM continues running after the line
Shut down initiated - total time = 60004ms
The main thread still runs, as well as the threads that we started in the test. In the
---------- BEGIN SOURCE ----------
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.stream.*;
/**
* Run with java -Xmx50g -Xms50g -XX:+UseZGC.
* <p>
* Once the Shut down is initiated, the JVM does not immediately
* stop, but the ZWorker threads are still running doing marking.
*
* @author Heinz M. Kabutz
*/
public class StrainingZGC {
private static final LongAdder total = new LongAdder();
private static final int SIZE = 20000;
private static final int THREADS = 200;
public static void main(String... args) throws InterruptedException {
Timer timer = new Timer(true);
timer.schedule(new TimerTask() {
public void run() {
System.exit(0);
}
}, 60_000);
long start = System.nanoTime();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.printf("Shut down initiated - total time = %dms%n",
((System.nanoTime() - start) / 1_000_000));
}));
for (int i = 0; i < THREADS; i++) {
int from = i * (SIZE / THREADS);
int to = Math.min(from + (SIZE / THREADS) * 10, SIZE);
Thread thread = new Thread(() -> {
List<List<Integer>> listOfLists = new ArrayList<>();
for (int j = 0; j < SIZE / THREADS; j++) {
listOfLists.add(
IntStream.range(0, SIZE)
.boxed()
.collect(Collectors.toCollection(LinkedList::new)));
}
while (true) {
total.add(listOfLists.stream().mapToLong(
list -> list.stream().mapToLong(Integer::longValue).sum()
).sum());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new CancellationException("interrupted");
}
}
});
thread.start();
}
while (true) {
System.out.println("total = " + total);
Thread.sleep(5000);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Don't create such a ridiculous object graph :)
FREQUENCY : always