8365972: JFR: ThreadDump and ClassLoaderStatistics events may cause back to back rotations

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2025-11-10 10:22:59 +00:00
parent 49f51f9450
commit 681dab7205
4 changed files with 145 additions and 5 deletions

View File

@ -48,6 +48,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<SettingDescriptor> settings = new ArrayList<>(5);
private final boolean dynamicSettings;
private final int stackTraceOffset;
@ -81,10 +82,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" :

View File

@ -263,7 +263,7 @@ public final class PlatformRecorder {
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 @@ public final class PlatformRecorder {
finishChunk(currentChunk, stopTime, null);
}
currentChunk = newChunk;
PeriodicEvents.doChunkBegin();
PeriodicEvents.doChunkBegin(false);
}
if (toDisk) {
@ -390,7 +390,7 @@ public final class PlatformRecorder {
finishChunk(currentChunk, timestamp, null);
}
currentChunk = newChunk;
PeriodicEvents.doChunkBegin();
PeriodicEvents.doChunkBegin(false);
}
private List<PlatformRecording> getRunningRecordings() {

View File

@ -69,12 +69,14 @@ public final class PeriodicEvents {
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);
}
}
}
}

View File

@ -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<Instant> threadDumps = Collections.synchronizedSet(new LinkedHashSet<>());
Set<Instant> classLoaderStatistics = Collections.synchronizedSet(new LinkedHashSet<>());
Set<Instant> 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<Instant> 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);
}
}
}