diff --git a/src/java.base/share/classes/java/lang/Shutdown.java b/src/java.base/share/classes/java/lang/Shutdown.java
index 36cf471a575cb..ed724254b6ea8 100644
--- a/src/java.base/share/classes/java/lang/Shutdown.java
+++ b/src/java.base/share/classes/java/lang/Shutdown.java
@@ -61,6 +61,8 @@ private static class Lock { };
     /* Lock object for the native halt method */
     private static Object haltLock = new Lock();
 
+    private static boolean loggedExitOrHalt = false;
+
     /**
      * Add a new system shutdown hook.  Checks the shutdown state and
      * the hook itself, but does not do any security checks.
@@ -145,6 +147,8 @@ private static void runHooks() {
      * It invokes the true native halt method.
      */
     static void halt(int status) {
+        logRuntimeExitOrHalt(status, false);         // Log without holding the lock;
+
         synchronized (haltLock) {
             halt0(status);
         }
@@ -157,7 +161,7 @@ static void halt(int status) {
      * which should pass a nonzero status code.
      */
     static void exit(int status) {
-        logRuntimeExit(status);         // Log without holding the lock;
+        logRuntimeExitOrHalt(status, true);         // Log without holding the lock;
 
         synchronized (Shutdown.class) {
             /* Synchronize on the class object, causing any other thread
@@ -169,21 +173,24 @@ static void exit(int status) {
         }
     }
 
-    /* Locate the logger and log the Runtime.exit(status).
+    /* Locate the logger and log the Runtime.exit(status) or Runtime.halt(status).
      * Catch and ignore any and all exceptions.
      */
-    private static void logRuntimeExit(int status) {
+    private static void logRuntimeExitOrHalt(int status, boolean isExit) {
+        if (loggedExitOrHalt) return;
+        String method = isExit ? "exit" : "halt";
         try {
             System.Logger log = System.getLogger("java.lang.Runtime");
             if (log.isLoggable(System.Logger.Level.DEBUG)) {
-                Throwable throwable = new Throwable("Runtime.exit(" + status + ")");
-                log.log(System.Logger.Level.DEBUG, "Runtime.exit() called with status: " + status,
+                Throwable throwable = new Throwable("Runtime." + method + "(" + status + ")");
+                log.log(System.Logger.Level.DEBUG, "Runtime." + method + "() called with status: " + status,
                         throwable);
+                loggedExitOrHalt = true;
             }
         } catch (Throwable throwable) {
             try {
                 // Exceptions from the Logger are printed but do not prevent exit
-                System.err.println("Runtime.exit(" + status + ") logging failed: " +
+                System.err.println("Runtime." + method + "(" + status + ") logging failed: " +
                         throwable.getMessage());
             } catch (Throwable throwable2) {
                 // Ignore
diff --git a/test/jdk/java/lang/RuntimeTests/RuntimeHaltLogTest.java b/test/jdk/java/lang/RuntimeTests/RuntimeHaltLogTest.java
new file mode 100644
index 0000000000000..00cc4dc2061ae
--- /dev/null
+++ b/test/jdk/java/lang/RuntimeTests/RuntimeHaltLogTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.StreamHandler;
+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 Runtime.halt.
+ * @run junit/othervm RuntimeHaltLogTest
+ */
+
+public class RuntimeHaltLogTest {
+
+    private static final String TEST_JDK = System.getProperty("test.jdk");
+    private static final String TEST_SRC = System.getProperty("test.src");
+
+    private static Object HOLD_LOGGER;
+
+    /**
+     * Call Runtime.halt() 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;
+        if (System.getProperty("ThrowingHandler") != null) {
+            HOLD_LOGGER = ThrowingHandler.installHandler();
+        }
+        Runtime.getRuntime().halt(status);
+    }
+
+    /**
+     * Test various log level settings, and none.
+     * @return a stream of arguments for parameterized test
+     */
+    private static Stream<Arguments> logParamProvider() {
+        return Stream.of(
+                // Logging enabled with level DEBUG
+                Arguments.of(List.of("-Djava.util.logging.config.file=" +
+                        Path.of(TEST_SRC, "ExitLogging-FINE.properties").toString()), 1,
+                        "Runtime.halt() called with status: 1"),
+                // Logging disabled due to level
+                Arguments.of(List.of("-Djava.util.logging.config.file=" +
+                        Path.of(TEST_SRC, "ExitLogging-INFO.properties").toString()), 2,
+                        ""),
+                // Console logger
+                Arguments.of(List.of("--limit-modules", "java.base",
+                        "-Djdk.system.logger.level=DEBUG"), 3,
+                        "Runtime.halt() called with status: 3"),
+                // Console logger
+                Arguments.of(List.of(), 4, ""),
+                // Throwing Handler
+                Arguments.of(List.of("-DThrowingHandler",
+                        "-Djava.util.logging.config.file=" +
+                        Path.of(TEST_SRC, "ExitLogging-FINE.properties").toString()), 5,
+                        "Runtime.halt(5) logging failed: Exception in publish")
+                );
+    }
+
+    /**
+     * Check that the logger output of a launched process contains the expected message.
+     * @param logProps The name of the log.properties file to set on the command line
+     * @param status the expected exit status of the process
+     * @param expectMessage log should contain the message
+     */
+    @ParameterizedTest
+    @MethodSource("logParamProvider")
+    public void checkLogger(List<String> logProps, int status, String expectMessage) {
+        ProcessBuilder pb = new ProcessBuilder();
+        pb.redirectErrorStream(true);
+
+        List<String> cmd = pb.command();
+        cmd.add(Path.of(TEST_JDK,"bin", "java").toString());
+        cmd.addAll(logProps);
+        cmd.add(this.getClass().getName());
+        cmd.add(Integer.toString(status));
+
+        try {
+            Process process = pb.start();
+            try (BufferedReader reader = process.inputReader()) {
+                List<String> lines = reader.lines().toList();
+                boolean match = (expectMessage.isEmpty())
+                        ? lines.size() == 0
+                        : lines.stream().filter(s -> s.contains(expectMessage)).findFirst().isPresent();
+                if (!match) {
+                    // Output lines for debug
+                    System.err.println("Expected: \"" + expectMessage + "\"");
+                    System.err.println("---- Actual output begin");
+                    lines.forEach(l -> System.err.println(l));
+                    System.err.println("---- Actual output end");
+                    fail("Unexpected log contents");
+                }
+            }
+            int result = process.waitFor();
+            assertEquals(status, result, "Exit status");
+        } catch (IOException | InterruptedException ex) {
+            fail(ex);
+        }
+    }
+
+    /**
+     * A LoggingHandler that throws an Exception.
+     */
+    public static class ThrowingHandler extends StreamHandler {
+
+        // Install this handler for java.lang.Runtime
+        public static Logger installHandler() {
+            Logger logger = Logger.getLogger("java.lang.Runtime");
+            logger.addHandler(new ThrowingHandler());
+            return logger;
+        }
+
+        @Override
+        public synchronized void publish(LogRecord record) {
+            super.publish(record);
+            throw new RuntimeException("Exception in publish");
+        }
+    }
+}