diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
index 8953976cd0c7a..d60baa3c1cc9a 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
@@ -30,6 +30,7 @@
 #include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/recorder/checkpoint/jfrMetadataEvent.hpp"
 #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp"
+#include "jfr/recorder/repository/jfrChunk.hpp"
 #include "jfr/recorder/repository/jfrRepository.hpp"
 #include "jfr/recorder/repository/jfrChunkRotation.hpp"
 #include "jfr/recorder/repository/jfrChunkWriter.hpp"
@@ -425,3 +426,7 @@ JVM_END
 JVM_ENTRY_NO_ENV(void, jfr_unregister_stack_filter(JNIEnv* env,  jclass jvm, jlong id))
   JfrStackFilterRegistry::remove(id);
 JVM_END
+
+NO_TRANSITION(jlong, jfr_nanos_now(JNIEnv* env, jclass jvm))
+  return JfrChunk::nanos_now();
+NO_TRANSITION_END
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
index 6a2d622d7e9a7..ca119c1f8c35a 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
@@ -165,6 +165,8 @@ jlong JNICALL jfr_register_stack_filter(JNIEnv* env, jclass jvm, jobjectArray cl
 
 jlong JNICALL jfr_unregister_stack_filter(JNIEnv* env, jclass jvm, jlong id);
 
+jlong JNICALL jfr_nanos_now(JNIEnv* env, jclass jvm);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
index 7ef831f6282f0..415c7468a6290 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
@@ -100,7 +100,8 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
       (char*)"hostTotalSwapMemory", (char*)"()J", (void*) jfr_host_total_swap_memory,
       (char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss,
       (char*)"registerStackFilter", (char*)"([Ljava/lang/String;[Ljava/lang/String;)J", (void*)jfr_register_stack_filter,
-      (char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter
+      (char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter,
+      (char*)"nanosNow", (char*)"()J", (void*)jfr_nanos_now
     };
 
     const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod);
diff --git a/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp
index 6d0ec7773b931..b88ba06bdf799 100644
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, 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
@@ -35,7 +35,7 @@ static const u2 JFR_VERSION_MAJOR = 2;
 static const u2 JFR_VERSION_MINOR = 1;
 
 // strictly monotone
-static jlong nanos_now() {
+jlong JfrChunk::nanos_now() {
   static jlong last = 0;
 
   jlong seconds;
@@ -147,7 +147,7 @@ void JfrChunk::update_start_ticks() {
 }
 
 void JfrChunk::update_start_nanos() {
-  const jlong now = nanos_now();
+  const jlong now = JfrChunk::nanos_now();
   assert(now >= _start_nanos, "invariant");
   assert(now >= _last_update_nanos, "invariant");
   _start_nanos = _last_update_nanos = now;
diff --git a/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp
index d7bd3411160d7..91b42948181ac 100644
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, 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
@@ -34,6 +34,8 @@ const u1 PAD = 0;
 class JfrChunk : public JfrCHeapObj {
   friend class JfrChunkWriter;
   friend class JfrChunkHeadWriter;
+ public:
+   static jlong nanos_now();
  private:
   char* _path;
   int64_t _start_ticks;
diff --git a/src/hotspot/share/jfr/support/jfrIntrinsics.hpp b/src/hotspot/share/jfr/support/jfrIntrinsics.hpp
index 65a94164a9756..6520f3cb00ca0 100644
--- a/src/hotspot/share/jfr/support/jfrIntrinsics.hpp
+++ b/src/hotspot/share/jfr/support/jfrIntrinsics.hpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
+* Copyright (c) 2012, 2024, 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
@@ -47,13 +47,13 @@ class JfrIntrinsicSupport : AllStatic {
 #define JFR_HAVE_INTRINSICS
 
 #define JFR_TEMPLATES(template)                                                                                      \
+  template(jdk_jfr_internal_HiddenWait,                               "jdk/jfr/internal/HiddenWait")                 \
   template(jdk_jfr_internal_JVM,                                      "jdk/jfr/internal/JVM")                        \
   template(jdk_jfr_internal_event_EventWriterFactory,                 "jdk/jfr/internal/event/EventWriterFactory")   \
   template(jdk_jfr_internal_event_EventConfiguration_signature,       "Ljdk/jfr/internal/event/EventConfiguration;") \
   template(getEventWriter_signature,                                  "()Ljdk/jfr/internal/event/EventWriter;")      \
   template(eventConfiguration_name,                                   "eventConfiguration")                          \
   template(commit_name,                                               "commit")                                      \
-  template(jfr_chunk_rotation_monitor,                                "jdk/jfr/internal/JVM$ChunkRotationMonitor")   \
 
 #define JFR_INTRINSICS(do_intrinsic, do_class, do_name, do_signature, do_alias)                                      \
   do_intrinsic(_counterTime,        jdk_jfr_internal_JVM, counterTime_name, void_long_signature, F_SN)               \
diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp
index 178f3e97d7108..ba463231592c5 100644
--- a/src/hotspot/share/runtime/objectMonitor.cpp
+++ b/src/hotspot/share/runtime/objectMonitor.cpp
@@ -1442,7 +1442,7 @@ bool ObjectMonitor::check_owner(TRAPS) {
 static inline bool is_excluded(const Klass* monitor_klass) {
   assert(monitor_klass != nullptr, "invariant");
   NOT_JFR_RETURN_(false);
-  JFR_ONLY(return vmSymbols::jfr_chunk_rotation_monitor() == monitor_klass->name();)
+  JFR_ONLY(return vmSymbols::jdk_jfr_internal_HiddenWait() == monitor_klass->name();)
 }
 
 static void post_monitor_wait_event(EventJavaMonitorWait* event,
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/HiddenWait.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/HiddenWait.java
new file mode 100644
index 0000000000000..26505990332c9
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/HiddenWait.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.internal;
+
+/**
+ * The HiddenWait class is used to exclude jdk.JavaMonitorWait events
+ * from being generated when Object.wait() is called on an object of this type.
+ */
+public final class HiddenWait {
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
index 02edfd1e5abbd..2e600c8c02974 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024, 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
@@ -41,14 +41,10 @@ public final class JVM {
 
     static final long RESERVED_CLASS_ID_LIMIT = 500;
 
-    private static class ChunkRotationMonitor {}
-
     /*
      * The JVM uses the chunk rotation monitor to notify Java that a rotation is warranted.
-     * The monitor type is used to exclude jdk.JavaMonitorWait events from being generated
-     * when Object.wait() is called on this monitor.
      */
-    public static final Object CHUNK_ROTATION_MONITOR = new ChunkRotationMonitor();
+    public static final Object CHUNK_ROTATION_MONITOR = new HiddenWait();
 
     private static volatile boolean nativeOK;
 
@@ -174,6 +170,11 @@ private static class ChunkRotationMonitor {}
      */
     public static native long getTicksFrequency();
 
+    /**
+     * Returns the same clock that sets the start time of a chunk (in nanos).
+     */
+    public static native long nanosNow();
+
     /**
      * Write message to log. Should swallow null or empty message, and be able
      * to handle any Java character and not crash with very large message
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java
index 036d49e6e0eb5..345d2fdcc8dfb 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2024, 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
@@ -119,11 +119,7 @@ private static void awaitUniqueTimestamp() {
                 lastTimestamp = time;
                 return;
             }
-            try {
-                Thread.sleep(0, 100);
-            } catch (InterruptedException iex) {
-                // ignore
-            }
+            Utils.takeNap(1);
         }
     }
 
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
index 7031718cb284e..75be70a0d1dae 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
@@ -64,6 +64,8 @@ public final class MetadataRepository {
     private boolean unregistered;
     private long lastUnloaded = -1;
 
+    private long lastMillis;
+
     public MetadataRepository() {
         initializeJVMEventTypes();
     }
@@ -313,11 +315,12 @@ synchronized Instant setOutput(String filename) {
         if (staleMetadata) {
             storeDescriptorInJVM();
         }
+        // Each chunk needs a unique timestamp. If two chunks get the same
+        // timestamp, the parser may stop prematurely at an earlier chunk.
+        // The resolution needs to be measured in milliseconds as this
+        // is what RecordingInfo:getStopTime() returns.
+        awaitEpochMilliShift();
         JVM.setOutput(filename);
-        // Each chunk needs a unique start timestamp and
-        // if the clock resolution is low, two chunks may
-        // get the same timestamp. Utils.getChunkStartNanos()
-        // ensures the timestamp is unique for the next chunk
         long chunkStart = JVMSupport.getChunkStartNanos();
         if (filename != null) {
             RepositoryFiles.notifyNewFile();
@@ -332,6 +335,18 @@ synchronized Instant setOutput(String filename) {
         return Utils.epochNanosToInstant(chunkStart);
     }
 
+    private void awaitEpochMilliShift() {
+        while (true) {
+            long nanos = JVM.nanosNow();
+            long millis = Utils.epochNanosToInstant(nanos).toEpochMilli();
+            if (millis != lastMillis) {
+                lastMillis = millis;
+                return;
+            }
+            Utils.takeNap(1);
+        }
+    }
+
     private void unregisterUnloaded() {
         long unloaded = JVM.getUnloadedEventClassCount();
         if (this.lastUnloaded != unloaded) {
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java
index e3b43635e3398..cdfc8f017a657 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024, 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
@@ -205,7 +205,7 @@ public final void awaitTermination(Duration timeout) throws InterruptedException
 
     protected abstract void process() throws IOException;
 
-    protected abstract boolean isRecording();
+    protected abstract boolean isRecordingStream();
 
     protected final void closeParser() {
         parserState.close();
@@ -249,7 +249,7 @@ private void startInternal(long startNanos) {
             if (streamConfiguration.started) {
                 throw new IllegalStateException("Event stream can only be started once");
             }
-            if (isRecording() && streamConfiguration.startTime == null) {
+            if (isRecordingStream() && streamConfiguration.startTime == null) {
                 streamConfiguration.setStartNanos(startNanos);
             }
             streamConfiguration.setStarted(true);
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java
index 3ed1c4c8d355b..bc9aa7987c3b5 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024, 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
@@ -33,9 +33,11 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
 import jdk.jfr.Configuration;
+import jdk.jfr.RecordingState;
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.jfr.internal.JVM;
 import jdk.jfr.internal.LogLevel;
@@ -59,6 +61,7 @@ public final class EventDirectoryStream extends AbstractEventStream {
     private final FileAccess fileAccess;
     private final PlatformRecording recording;
     private final StreamBarrier barrier = new StreamBarrier();
+    private final AtomicLong streamId = new AtomicLong();
     private ChunkParser currentParser;
     private long currentChunkStartNanos;
     private RecordedEvent[] sortedCache;
@@ -80,6 +83,8 @@ public EventDirectoryStream(
         }
         this.fileAccess = Objects.requireNonNull(fileAccess);
         this.repositoryFiles = new RepositoryFiles(fileAccess, p, allowSubDirectories);
+        this.streamId.incrementAndGet();
+        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Stream " + streamId + " started.");
     }
 
     @Override
@@ -137,13 +142,14 @@ protected void processRecursionSafe() throws IOException {
         Dispatcher lastDisp = null;
         Dispatcher disp = dispatcher();
         Path path;
-        boolean validStartTime = isRecording() || disp.startTime != null;
+        boolean validStartTime = isRecordingStream() || disp.startTime != null;
         if (validStartTime) {
             path = repositoryFiles.firstPath(disp.startNanos, true);
         } else {
             path = repositoryFiles.lastPath(true);
         }
         if (path == null) { // closed
+            logStreamEnd("no first chunk file found.");
             return;
         }
         currentChunkStartNanos = repositoryFiles.getTimestamp(path);
@@ -168,7 +174,10 @@ protected void processRecursionSafe() throws IOException {
                         processUnordered(disp);
                     }
                     currentParser.resetCache();
-                    if (currentParser.getLastFlush() > filterEnd) {
+                    long lastFlush = currentParser.getLastFlush();
+                    if (lastFlush  > filterEnd) {
+                        logStreamEnd("end time at " + filterEnd +
+                                     "ns (epoch), parser at " + lastFlush + "ns (epoch).");
                         return;
                     }
                 }
@@ -177,20 +186,25 @@ protected void processRecursionSafe() throws IOException {
 
                 barrier.check(); // block if recording is being stopped
                 if (barrier.getStreamEnd() <= endMillis) {
+                    String msg = "stopped at " + barrier.getStreamEnd() + "ms (epoch), ";
+                    msg += "parser at " + endMillis + "ms (epoch), " + endNanos + "ns (epoch)";
+                    logStreamEnd(msg);
                     return;
                 }
 
-                if (!barrier.hasStreamEnd() && isLastChunk()) {
-                    // Recording was stopped/closed externally, and no more data to process.
-                    return;
+                if (isRecordingStream()) {
+                    if (recording.getState() == RecordingState.STOPPED && !barrier.used()) {
+                        logStreamEnd("recording stopped externally.");
+                        return;
+                    }
                 }
 
                 if (repositoryFiles.hasFixedPath() && currentParser.isFinalChunk()) {
-                    // JVM process exited/crashed, or repository migrated to an unknown location
+                    logStreamEnd("JVM process exited/crashed, or repository migrated to an unknown location.");
                     return;
                 }
                 if (isClosed()) {
-                    // Stream was closed
+                    logStreamEnd("stream closed.");
                     return;
                 }
                 long durationNanos = currentParser.getChunkDuration();
@@ -205,7 +219,8 @@ protected void processRecursionSafe() throws IOException {
                 }
                 path = repositoryFiles.nextPath(currentChunkStartNanos + durationNanos, true);
                 if (path == null) {
-                    return; // stream closed
+                    logStreamEnd("no more chunk files found.");
+                    return;
                 }
                 currentChunkStartNanos = repositoryFiles.getTimestamp(path);
                 input.setFile(path);
@@ -217,15 +232,12 @@ protected void processRecursionSafe() throws IOException {
         }
     }
 
-
-    private boolean isLastChunk() {
-        if (!isRecording()) {
-            return false;
-        }
-        return recording.getFinalChunkStartNanos() >= currentParser.getStartNanos();
+    private void logStreamEnd(String text) {
+        String msg = "Stream " + streamId + " ended, " + text;
+        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, msg);
     }
 
-    protected boolean isRecording() {
+    protected boolean isRecordingStream() {
         return recording != null;
     }
 
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java
index 9ecaf2a6075e5..f03e8d8acb48e 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024, 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
@@ -73,7 +73,7 @@ public void close() {
     }
 
     @Override
-    protected boolean isRecording() {
+    protected boolean isRecordingStream() {
         return false;
     }
 
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java
index ed94a908d47a4..0b3132da2178b 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, 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
@@ -41,6 +41,7 @@
 public final class StreamBarrier implements Closeable {
 
     private boolean activated = false;
+    private boolean used = false;
     private long end = Long.MAX_VALUE;
 
     // Blocks thread until barrier is deactivated
@@ -62,12 +63,9 @@ public synchronized long getStreamEnd() {
         return end;
     }
 
-    public synchronized boolean hasStreamEnd() {
-        return end != Long.MAX_VALUE;
-    }
-
     public synchronized void activate() {
         activated = true;
+        used = true;
     }
 
     @Override
@@ -75,4 +73,11 @@ public synchronized void close() throws IOException {
         activated = false;
         this.notifyAll();
     }
+
+    /**
+     * Returns {@code true) if barrier is, or has been, in active state, {@code false) otherwise.
+     */
+    public synchronized boolean used() {
+        return used;
+    }
 }
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java
index 0049662e9a1f6..5e04a25fe7ddf 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java
@@ -48,6 +48,7 @@
 import jdk.jfr.Event;
 import jdk.jfr.EventType;
 import jdk.jfr.RecordingState;
+import jdk.jfr.internal.HiddenWait;
 import jdk.jfr.internal.LogLevel;
 import jdk.jfr.internal.LogTag;
 import jdk.jfr.internal.Logger;
@@ -351,8 +352,11 @@ private static boolean isSupportedType(Class<?> type) {
     }
 
     public static void takeNap(long millis) {
+        HiddenWait hiddenWait = new HiddenWait();
         try {
-            Thread.sleep(millis);
+            synchronized(hiddenWait) {
+                hiddenWait.wait(millis);
+            }
         } catch (InterruptedException e) {
             // ok
         }
diff --git a/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStop.java b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStop.java
index 58dcbbcbe2079..36bbaa3c557db 100644
--- a/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStop.java
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStop.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, 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
@@ -41,7 +41,7 @@
  * @requires vm.hasJFR
  * @library /test/lib /test/jdk
  * @build jdk.jfr.api.consumer.recordingstream.EventProducer
- * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStop
+ * @run main/othervm -Xlog:system+parser+jfr=info jdk.jfr.api.consumer.recordingstream.TestStop
  */
 public class TestStop {
     static class StopEvent extends Event {
@@ -139,41 +139,50 @@ private static void testNestedStop() throws Exception {
                 Path fileInner = Path.of("inner.jfr");
                 inner.dump(fileInner);
                 outer.dump(fileOuter);
-                System.out.println("Outer dump:");
                 var dumpOuter = RecordingFile.readAllEvents(fileOuter);
-                for (RecordedEvent e : dumpOuter) {
-                    System.out.println(eventToText(e));
-                }
-                System.out.println("Inner dump:");
                 var dumpInner = RecordingFile.readAllEvents(fileInner);
-                for (RecordedEvent e : dumpInner) {
-                    System.out.println(eventToText(e));
-                }
-                System.out.println();
-                System.out.println("Outer stream:");
-                for (String s : outerStream) {
-                    System.out.println(s);
-                }
-                System.out.println("Inner stream:");
-                for (String s : innerStream) {
-                    System.out.println(s);
-                }
+
                 if (dumpOuter.size() != 3) {
+                    log(outerStream, innerStream, dumpOuter, dumpInner);
                     throw new AssertionError("Expected outer dump to have 3 events");
                 }
                 if (outerStream.size() != 3) {
+                    log(outerStream, innerStream, dumpOuter, dumpInner);
                     throw new AssertionError("Expected outer stream to have 3 events");
                 }
                 if (dumpInner.size() != 1) {
+                    log(outerStream, innerStream, dumpOuter, dumpInner);
                     throw new AssertionError("Expected inner dump to have 1 event");
                 }
                 if (innerStream.size() != 1) {
+                    log(outerStream, innerStream, dumpOuter, dumpInner);
                     throw new AssertionError("Expected inner stream to have 1 event");
                 }
             }
         }
     }
 
+    private static void log(List<String> outerStream, List<String> innerStream, List<RecordedEvent> dumpOuter,
+            List<RecordedEvent> dumpInner) {
+        System.out.println("Outer dump:");
+        for (RecordedEvent e : dumpOuter) {
+            System.out.println(eventToText(e));
+        }
+        System.out.println("Inner dump:");
+        for (RecordedEvent e : dumpInner) {
+            System.out.println(eventToText(e));
+        }
+        System.out.println();
+        System.out.println("Outer stream:");
+        for (String s : outerStream) {
+            System.out.println(s);
+        }
+        System.out.println("Inner stream:");
+        for (String s : innerStream) {
+            System.out.println(s);
+        }
+    }
+
     private static String eventToText(RecordedEvent event) {
         Instant timestamp = event.getEndTime();
         long s = timestamp.getEpochSecond();