diff --git a/src/java.base/share/classes/java/lang/Runtime.java b/src/java.base/share/classes/java/lang/Runtime.java index 25d2351a24e..2a5c37b2ae9 100644 --- a/src/java.base/share/classes/java/lang/Runtime.java +++ b/src/java.base/share/classes/java/lang/Runtime.java @@ -156,6 +156,11 @@ public class Runtime { *
The {@link System#exit(int) System.exit} method is the
* conventional and convenient means of invoking this method.
*
+ * @implNote
+ * If the {@linkplain System#getLogger(String) system logger} for {@code java.lang.Runtime}
+ * is enabled with logging level {@link System.Logger.Level#DEBUG Level.DEBUG} the stack trace
+ * of the call to {@code Runtime.exit()} is logged.
+ *
* @param status
* Termination status. By convention, a nonzero status code
* indicates abnormal termination.
diff --git a/src/java.base/share/classes/java/lang/Shutdown.java b/src/java.base/share/classes/java/lang/Shutdown.java
index aa37689e505..4dc63a46714 100644
--- a/src/java.base/share/classes/java/lang/Shutdown.java
+++ b/src/java.base/share/classes/java/lang/Shutdown.java
@@ -157,16 +157,36 @@ class Shutdown {
* which should pass a nonzero status code.
*/
static void exit(int status) {
+ System.Logger log = getRuntimeExitLogger(); // Locate the logger without holding the lock;
synchronized (Shutdown.class) {
/* Synchronize on the class object, causing any other thread
* that attempts to initiate shutdown to stall indefinitely
*/
+ if (log != null) {
+ Throwable throwable = new Throwable("Runtime.exit(" + status + ")");
+ log.log(System.Logger.Level.DEBUG, "Runtime.exit() called with status: " + status,
+ throwable);
+ }
beforeHalt();
runHooks();
halt(status);
}
}
+ /* Locate and return the logger for Shutdown.exit, if it is functional and DEBUG enabled.
+ * Exceptions should not prevent System.exit; the exception is printed and otherwise ignored.
+ */
+ private static System.Logger getRuntimeExitLogger() {
+ try {
+ System.Logger log = System.getLogger("java.lang.Runtime");
+ return (log.isLoggable(System.Logger.Level.DEBUG)) ? log : null;
+ } catch (Throwable throwable) {
+ // Exceptions from locating the Logger are printed but do not prevent exit
+ System.err.println("Runtime.exit() log finder failed with: " + throwable.getMessage());
+ }
+ return null;
+ }
+
/* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon
* thread has finished. Unlike the exit method, this method does not
diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java
index 51d688e0d4a..501ed47fcad 100644
--- a/src/java.base/share/classes/java/lang/System.java
+++ b/src/java.base/share/classes/java/lang/System.java
@@ -1902,6 +1902,9 @@ public final class System {
* Runtime.getRuntime().exit(n)
*
*
+ * @implNote
+ * The initiation of the shutdown sequence is logged by {@link Runtime#exit(int)}.
+ *
* @param status exit status.
* @throws SecurityException
* if a security manager exists and its {@code checkExit} method
diff --git a/test/jdk/java/lang/RuntimeTests/ExitLogging-FINE.properties b/test/jdk/java/lang/RuntimeTests/ExitLogging-FINE.properties
new file mode 100644
index 00000000000..21b19fa5ac8
--- /dev/null
+++ b/test/jdk/java/lang/RuntimeTests/ExitLogging-FINE.properties
@@ -0,0 +1,8 @@
+############################################################
+# Enable logging java.lang.Runtime to the console
+############################################################
+
+handlers= java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = ALL
+java.lang.Runtime.level = FINE
diff --git a/test/jdk/java/lang/RuntimeTests/ExitLogging-INFO.properties b/test/jdk/java/lang/RuntimeTests/ExitLogging-INFO.properties
new file mode 100644
index 00000000000..212d9bd86d2
--- /dev/null
+++ b/test/jdk/java/lang/RuntimeTests/ExitLogging-INFO.properties
@@ -0,0 +1,8 @@
+############################################################
+# Enable logging java.lang.Runtime to the console
+############################################################
+
+handlers= java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = ALL
+java.lang.Runtime.level = INFO
diff --git a/test/jdk/java/lang/RuntimeTests/RuntimeExitLogTest.java b/test/jdk/java/lang/RuntimeTests/RuntimeExitLogTest.java
new file mode 100644
index 00000000000..7e39ae5cec1
--- /dev/null
+++ b/test/jdk/java/lang/RuntimeTests/RuntimeExitLogTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.ParameterizedTest;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/*
+ * @test
+ * @summary verify logging of call to System.exit or Runtime.exit.
+ * @run junit/othervm RuntimeExitLogTest
+ */
+
+public class RuntimeExitLogTest {
+
+ private static final String TEST_JDK = System.getProperty("test.jdk");
+ private static final String TEST_SRC = System.getProperty("test.src");
+
+ /**
+ * Call System.exit() with the parameter (or zero if not supplied).
+ * @param args zero or 1 argument, an exit status
+ */
+ public static void main(String[] args) throws InterruptedException {
+ int status = args.length > 0 ? Integer.parseInt(args[0]) : 0;
+ System.exit(status);
+ }
+
+ /**
+ * Test various log level settings, and none.
+ * @return a stream of arguments for parameterized test
+ */
+ private static Stream