diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 40876fae4e1..9b988139d44 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -49,6 +49,7 @@ public final class PlatformEventType extends Type { private final boolean isJDK; private final boolean isMethodSampling; private final boolean isCPUTimeMethodSampling; + private final boolean isBackToBackSensitive; private final List settings = new ArrayList<>(5); private final boolean dynamicSettings; private final int stackTraceOffset; @@ -82,10 +83,25 @@ public final class PlatformEventType extends Type { this.isJVM = Type.isDefinedByJVM(id); this.isMethodSampling = determineMethodSampling(); this.isCPUTimeMethodSampling = isJVM && name.equals(Type.EVENT_NAME_PREFIX + "CPUTimeSample"); + this.isBackToBackSensitive = determineBackToBackSensitive(); this.isJDK = isJDK; this.stackTraceOffset = determineStackTraceOffset(); } + private boolean determineBackToBackSensitive() { + if (getName().equals(Type.EVENT_NAME_PREFIX + "ThreadDump")) { + return true; + } + if (getName().equals(Type.EVENT_NAME_PREFIX + "ClassLoaderStatistics")) { + return true; + } + return false; + } + + public boolean isBackToBackSensitive() { + return isBackToBackSensitive; + } + private boolean isExceptionEvent() { switch (getName()) { case Type.EVENT_NAME_PREFIX + "JavaErrorThrow" : diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java index 2184d85cf34..cf46c05b804 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java @@ -263,7 +263,7 @@ synchronized long start(PlatformRecording recording) { if (toDisk) { PeriodicEvents.setFlushInterval(streamInterval); } - PeriodicEvents.doChunkBegin(); + PeriodicEvents.doChunkBegin(true); Duration duration = recording.getDuration(); if (duration != null) { recording.setStopTime(startTime.plus(duration)); @@ -335,7 +335,7 @@ synchronized void stop(PlatformRecording recording) { finishChunk(currentChunk, stopTime, null); } currentChunk = newChunk; - PeriodicEvents.doChunkBegin(); + PeriodicEvents.doChunkBegin(false); } if (toDisk) { @@ -390,7 +390,7 @@ synchronized void rotateDisk() { finishChunk(currentChunk, timestamp, null); } currentChunk = newChunk; - PeriodicEvents.doChunkBegin(); + PeriodicEvents.doChunkBegin(false); } private List getRunningRecordings() { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/PeriodicEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/PeriodicEvents.java index 8cbf6334e64..92033dcce70 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/PeriodicEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/PeriodicEvents.java @@ -69,12 +69,14 @@ public static boolean removeEvent(Runnable runnable) { return taskRepository.removeTask(runnable); } - public static void doChunkBegin() { + public static void doChunkBegin(boolean startRecording) { long timestamp = JVM.counterTime(); for (EventTask task : taskRepository.getTasks()) { var eventType = task.getEventType(); if (eventType.isEnabled() && eventType.isBeginChunk()) { - task.run(timestamp, PeriodicType.BEGIN_CHUNK); + if (!eventType.isBackToBackSensitive() || startRecording) { + task.run(timestamp, PeriodicType.BEGIN_CHUNK); + } } } } diff --git a/test/jdk/jdk/jfr/event/runtime/TestBackToBackSensitive.java b/test/jdk/jdk/jfr/event/runtime/TestBackToBackSensitive.java new file mode 100644 index 00000000000..91fcd60f87d --- /dev/null +++ b/test/jdk/jdk/jfr/event/runtime/TestBackToBackSensitive.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 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 + * 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.runtime; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import jdk.jfr.Configuration; +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.StackTrace; +import jdk.jfr.consumer.RecordedClassLoader; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary The test verifies that jdk.ClassLoaderStatistics and + * jdk.ThreadThreadDump are not emitted at the beginning of a chunk + * when the period is everyChunk, as is the case in default.jfc + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.event.runtime.TestBackToBackSensitive + */ +public class TestBackToBackSensitive { + @StackTrace(false) + static class FillEvent extends Event { + String message; + } + + public static void main(String... arg) throws Exception { + Set threadDumps = Collections.synchronizedSet(new LinkedHashSet<>()); + Set classLoaderStatistics = Collections.synchronizedSet(new LinkedHashSet<>()); + Set physicalMemory = Collections.synchronizedSet(new LinkedHashSet<>()); + + Configuration configuration = Configuration.getConfiguration("default"); + try (RecordingStream r1 = new RecordingStream(configuration)) { + r1.setMaxSize(Long.MAX_VALUE); + r1.onEvent("jdk.ThreadDump", e -> threadDumps.add(e.getStartTime())); + r1.onEvent("jdk.ClassLoaderStatistics", e -> { + RecordedClassLoader cl = e.getValue("classLoader"); + if (cl != null) { + if (cl.getType().getName().contains("PlatformClassLoader")) { + classLoaderStatistics.add(e.getStartTime()); + } + } + }); + r1.onEvent("jdk.PhysicalMemory", e -> physicalMemory.add(e.getStartTime())); + // Start chunk 1 + r1.startAsync(); + try (Recording r2 = new Recording()) { + // Start chunk 2 + r2.start(); + // Starts chunk 3 + r2.stop(); + } + // Start chunk 4 by filling up chunk 3 + for (int i = 0; i < 1_500_000; i++) { + FillEvent f = new FillEvent(); + f.commit(); + } + r1.stop(); + long chunkFiles = filesInRepository(); + System.out.println("Number of chunk files: " + chunkFiles); + // When jdk.ClassLoaderStatistics and jdk.ThreadThreadDump are expected to be + // emitted: + // Chunk 1: begin, end + // Chunk 2: begin, end + // Chunk 3: end + // Chunk 4: end + assertCount("jdk.ThreadDump", threadDumps, 2 + 2 + (chunkFiles - 2)); + assertCount("jdk.ClassLoaderStatistics", classLoaderStatistics, 2 + 2 + (chunkFiles - 2)); + // When jdk.PhysicalMemory is expected to be emitted: + // Chunk 1: begin, end + // Chunk 2: begin, end + // Chunk 3: begin, end + // Chunk 4: begin, end + assertCount("jdk.PhysicalMemory", physicalMemory, 2 * chunkFiles); + } + } + + private static long filesInRepository() throws IOException { + Path repository = Path.of(System.getProperty("jdk.jfr.repository")); + return Files.list(repository).filter(p -> p.toString().endsWith(".jfr")).count(); + } + + private static void assertCount(String eventName, Set timestamps, long expected) throws Exception { + System.out.println("Timestamps for " + eventName + ":"); + for (Instant timestamp : timestamps) { + System.out.println(timestamp); + } + int count = timestamps.size(); + if (count != expected) { + throw new Exception("Expected " + expected + " timestamps for event " + eventName + ", but got " + count); + } + } +}