diff --git a/src/hotspot/share/jfr/jfr.cpp b/src/hotspot/share/jfr/jfr.cpp index 99faa108bfd..cf4e1e0bf81 100644 --- a/src/hotspot/share/jfr/jfr.cpp +++ b/src/hotspot/share/jfr/jfr.cpp @@ -122,9 +122,9 @@ void Jfr::on_resolution(const Method* caller, const Method* target, TRAPS) { } #endif -void Jfr::on_vm_shutdown(bool exception_handler, bool halt) { +void Jfr::on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown, bool halt) { if (!halt && JfrRecorder::is_recording()) { - JfrEmergencyDump::on_vm_shutdown(exception_handler); + JfrEmergencyDump::on_vm_shutdown(emit_old_object_samples, emit_event_shutdown); } } diff --git a/src/hotspot/share/jfr/jfr.hpp b/src/hotspot/share/jfr/jfr.hpp index 89ecf5bc798..c8a9d733800 100644 --- a/src/hotspot/share/jfr/jfr.hpp +++ b/src/hotspot/share/jfr/jfr.hpp @@ -66,7 +66,7 @@ class Jfr : AllStatic { static void on_resolution(const Method* caller, const Method* target, TRAPS); static void on_java_thread_start(JavaThread* starter, JavaThread* startee); static void on_set_current_thread(JavaThread* jt, oop thread); - static void on_vm_shutdown(bool exception_handler = false, bool halt = false); + static void on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown, bool halt = false); static void on_vm_error_report(outputStream* st); static bool on_flight_recorder_option(const JavaVMOption** option, char* delimiter); static bool on_start_flight_recording_option(const JavaVMOption** option, char* delimiter); diff --git a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp index e0bbd8a6ddc..bd15672a6fd 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp @@ -559,22 +559,22 @@ class JavaThreadInVMAndNative : public StackObj { } }; -static void post_events(bool exception_handler, Thread* thread) { - if (exception_handler) { +static void post_events(bool emit_old_object_samples, bool emit_event_shutdown, Thread* thread) { + if (emit_old_object_samples) { + LeakProfiler::emit_events(max_jlong, false, false); + } + if (emit_event_shutdown) { EventShutdown e; e.set_reason("VM Error"); e.commit(); - } else { - // OOM - LeakProfiler::emit_events(max_jlong, false, false); } EventDumpReason event; - event.set_reason(exception_handler ? "Crash" : "Out of Memory"); + event.set_reason(emit_old_object_samples ? "Out of Memory" : "Crash"); event.set_recordingId(-1); event.commit(); } -void JfrEmergencyDump::on_vm_shutdown(bool exception_handler) { +void JfrEmergencyDump::on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown) { if (!guard_reentrancy()) { return; } @@ -587,7 +587,7 @@ void JfrEmergencyDump::on_vm_shutdown(bool exception_handler) { if (!prepare_for_emergency_dump(thread)) { return; } - post_events(exception_handler, thread); + post_events(emit_old_object_samples, emit_event_shutdown, thread); // if JavaThread, transition to _thread_in_native to issue a final flushpoint NoHandleMark nhm; jtivm.transition_to_native(); diff --git a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp index 7db4b511746..04c2851a516 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -39,7 +39,7 @@ class JfrEmergencyDump : AllStatic { static const char* chunk_path(const char* repository_path); static void on_vm_error(const char* repository_path); static void on_vm_error_report(outputStream* st, const char* repository_path); - static void on_vm_shutdown(bool exception_handler); + static void on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown); }; #endif // SHARE_JFR_RECORDER_REPOSITORY_JFREMERGENCYDUMP_HPP diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index a92a1c5d285..2c512cb801a 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -471,7 +471,10 @@ void before_exit(JavaThread* thread, bool halt) { event.commit(); } - JFR_ONLY(Jfr::on_vm_shutdown(false, halt);) + // 2nd argument (emit_event_shutdown) should be set to false + // because EventShutdown would be emitted at Threads::destroy_vm(). + // (one of the callers of before_exit()) + JFR_ONLY(Jfr::on_vm_shutdown(true, false, halt);) // Stop the WatcherThread. We do this before disenrolling various // PeriodicTasks to reduce the likelihood of races. diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 5cf37f14445..0349401fdb9 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -1854,7 +1854,7 @@ void VMError::report_and_die(int id, const char* message, const char* detail_fmt log.set_fd(-1); } - JFR_ONLY(Jfr::on_vm_shutdown(true);) + JFR_ONLY(Jfr::on_vm_shutdown(static_cast(_id) == OOM_JAVA_HEAP_FATAL, true);) if (PrintNMTStatistics) { fdStream fds(fd_out); diff --git a/test/jdk/jdk/jfr/event/oldobject/TestEmergencyDumpAtOOM.java b/test/jdk/jdk/jfr/event/oldobject/TestEmergencyDumpAtOOM.java new file mode 100644 index 00000000000..a86229c65e3 --- /dev/null +++ b/test/jdk/jdk/jfr/event/oldobject/TestEmergencyDumpAtOOM.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, NTT DATA. + * 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. + */ + +package jdk.jfr.event.oldobject; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordingFile; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** +* @test +* @bug 8364090 +* @summary Tests Dump reason and OldObjectSample events at OOME. +* @requires vm.flagless +* @requires vm.hasJFR +* @library /test/lib +* @run main/othervm jdk.jfr.event.oldobject.TestEmergencyDumpAtOOM +*/ +public class TestEmergencyDumpAtOOM { + + public static List DEFAULT_LEAKER_ARGS = List.of( + "-Xmx64m", + "-XX:TLABSize=2k", + "-XX:StartFlightRecording:dumponexit=true,filename=oom.jfr", + Leaker.class.getName() + ); + + public static class Leaker { + public static void main(String... args) { + List list = new ArrayList<>(); + while (true) { + list.add(new byte[1024]); + } + } + } + + private static void test(boolean shouldCrash) throws Exception { + List args = new ArrayList<>(DEFAULT_LEAKER_ARGS); + if (shouldCrash) { + args.add(0, "-XX:+CrashOnOutOfMemoryError"); + } + + while (true) { + Process p = ProcessTools.createTestJavaProcessBuilder(args).start(); + p.waitFor(); + OutputAnalyzer output = new OutputAnalyzer(p); + if (!output.contains("java.lang.OutOfMemoryError")) { + throw new RuntimeException("OutOfMemoryError did not happen."); + } + + // Check recording file + String jfrFileName = shouldCrash ? String.format("hs_err_pid%d.jfr", p.pid()) : "oom.jfr"; + Path jfrPath = Path.of(jfrFileName); + Asserts.assertTrue(Files.exists(jfrPath), "No jfr recording file " + jfrFileName + " exists"); + + // Check events + AtomicLong oldObjects = new AtomicLong(); + AtomicReference shutdownReason = new AtomicReference<>(); + AtomicReference dumpReason = new AtomicReference<>(); + try (EventStream stream = EventStream.openFile(jfrPath)) { + stream.onEvent("jdk.OldObjectSample", e -> oldObjects.incrementAndGet()); + stream.onEvent("jdk.Shutdown", e -> shutdownReason.set(e.getString("reason"))); + stream.onEvent("jdk.DumpReason", e -> dumpReason.set(e.getString("reason"))); + stream.start(); + } + + // Check OldObjectSample events + if (oldObjects.get() > 0L) { + if (shouldCrash) { + Asserts.assertEquals("VM Error", shutdownReason.get()); + Asserts.assertEquals("Out of Memory", dumpReason.get()); + } else { + Asserts.assertEquals("No remaining non-daemon Java threads", shutdownReason.get()); + } + // Passed this test + return; + } + + System.out.println("Could not find OldObjectSample events. Retrying."); + } + } + + public static void main(String... args) throws Exception { + test(true); + test(false); + } +}