From 53734a31b2ee7a22c961be4d6ff76f8c8cd8d69f Mon Sep 17 00:00:00 2001 From: mgronlun Date: Wed, 15 Apr 2026 18:23:16 +0200 Subject: [PATCH 1/4] 8382242 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Rouél --- .../jfr/internal/consumer/ParserFactory.java | 9 +- ...aReconstructionWithRetainedStringPool.java | 123 ++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java index e7dd234ca8d0e..c973dadb3b7f3 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java @@ -132,9 +132,12 @@ private Parser createPrimitiveParser(Type type, boolean event) throws IOExceptio case "short" -> new ShortParser(); case "byte" -> new ByteParser(); case "java.lang.String" -> { - ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type); - ConstantLookup lookup = new ConstantLookup(pool, type); - constantLookups.put(type.getId(), lookup); + ConstantLookup lookup = constantLookups.get(type.getId()); + if (lookup == null) { + ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type); + lookup = new ConstantLookup(pool, type); + constantLookups.put(type.getId(), lookup); + } yield new StringParser(lookup, event); } default -> throw new IOException("Unknown primitive type " + type.getName()); diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java new file mode 100644 index 0000000000000..bc43792f30866 --- /dev/null +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2026, 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.api.consumer.streaming; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test that it is possible to register new metadata in a new segment while retaining the string pool. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.streaming.TestMetadataReconstructionWithRetainedStringPool + */ +public class TestMetadataReconstructionWithRetainedStringPool { + + static final class EventA extends Event { + String text; + } + + static final class EventB extends Event { + String text; + } + + /// Minimum string length required to trigger StringPool usage. + /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. + private static final int STRING_POOL_MIN_LIMIT = 16; + + public static void main(String... args) throws InterruptedException { + var aEventsPosted = new CountDownLatch(1); + var readyToPostEventB = new CountDownLatch(1); + var allEventsProcessed = new CountDownLatch(1); + int expectedEvents = 3; + var eventsRemaining = new AtomicInteger(expectedEvents); + + // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding. + var text = "a".repeat(STRING_POOL_MIN_LIMIT + 1); + + try (var rs = new RecordingStream()) { + rs.onEvent(e -> { + int remaining = eventsRemaining.decrementAndGet(); + String textValue = e.getValue("text"); + if (textValue == null) { + throw new RuntimeException("e.getValue(\"text\") returned null"); + } + System.out.printf("Event #%d [%s]: text=%s%n", + expectedEvents - remaining, + e.getEventType().getName(), + textValue); + + if (remaining == 0) { + allEventsProcessed.countDown(); + } + }); + + rs.onFlush(() -> { + if (aEventsPosted.getCount() == 0) { + readyToPostEventB.countDown(); + } + }); + + rs.startAsync(); + + // Condition 2: Two distinct event types are required. + // First, load EventA as the initial event type and emit its first event. + // This first event looks into the StringPool pre-cache. Although the + // string length qualifies for pooling, because it isn't pre-cached, + // the first event encodes the string inline. + // The second event finds the string in the pre-cache and adds it to the + // pool. A constant pool ID to the pooled string is encoded in the event. + // + emit(new EventA(), text); + emit(new EventA(), text); + aEventsPosted.countDown(); + + // Condition 3: Wait for JFR flush. + // The default flush period is ~1 second. + readyToPostEventB.await(); + + // Load the second event type, EventB, AFTER the flush segment containing the two EventA events. + // A new metadata description will be constructed, and we verify that the StringPool reference added in the previous + // segment is still available for the EventB string pool reference to be resolved correctly. + emit(new EventB(), text); + + allEventsProcessed.await(); + } + } + + private static void emit(Event event, String text) { + if (event instanceof EventA a) { + a.text = text; + } + if (event instanceof EventB b) { + b.text = text; + } + event.commit(); + } +} From af6aa1a9309aba4565d8ab44eebe291efba7f6b3 Mon Sep 17 00:00:00 2001 From: mgronlun Date: Thu, 16 Apr 2026 10:15:32 +0200 Subject: [PATCH 2/4] adjust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Rouél --- ...aReconstructionWithRetainedStringPool.java | 246 +++++++++--------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java index bc43792f30866..a9b4247a96423 100644 --- a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java @@ -1,123 +1,123 @@ -/* - * Copyright (c) 2026, 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.api.consumer.streaming; - -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.CountDownLatch; - -import jdk.jfr.Event; -import jdk.jfr.consumer.RecordingStream; - -/** - * @test - * @summary Test that it is possible to register new metadata in a new segment while retaining the string pool. - * @requires vm.flagless - * @requires vm.hasJFR - * @library /test/lib - * @run main/othervm jdk.jfr.api.consumer.streaming.TestMetadataReconstructionWithRetainedStringPool - */ -public class TestMetadataReconstructionWithRetainedStringPool { - - static final class EventA extends Event { - String text; - } - - static final class EventB extends Event { - String text; - } - - /// Minimum string length required to trigger StringPool usage. - /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. - private static final int STRING_POOL_MIN_LIMIT = 16; - - public static void main(String... args) throws InterruptedException { - var aEventsPosted = new CountDownLatch(1); - var readyToPostEventB = new CountDownLatch(1); - var allEventsProcessed = new CountDownLatch(1); - int expectedEvents = 3; - var eventsRemaining = new AtomicInteger(expectedEvents); - - // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding. - var text = "a".repeat(STRING_POOL_MIN_LIMIT + 1); - - try (var rs = new RecordingStream()) { - rs.onEvent(e -> { - int remaining = eventsRemaining.decrementAndGet(); - String textValue = e.getValue("text"); - if (textValue == null) { - throw new RuntimeException("e.getValue(\"text\") returned null"); - } - System.out.printf("Event #%d [%s]: text=%s%n", - expectedEvents - remaining, - e.getEventType().getName(), - textValue); - - if (remaining == 0) { - allEventsProcessed.countDown(); - } - }); - - rs.onFlush(() -> { - if (aEventsPosted.getCount() == 0) { - readyToPostEventB.countDown(); - } - }); - - rs.startAsync(); - - // Condition 2: Two distinct event types are required. - // First, load EventA as the initial event type and emit its first event. - // This first event looks into the StringPool pre-cache. Although the - // string length qualifies for pooling, because it isn't pre-cached, - // the first event encodes the string inline. - // The second event finds the string in the pre-cache and adds it to the - // pool. A constant pool ID to the pooled string is encoded in the event. - // - emit(new EventA(), text); - emit(new EventA(), text); - aEventsPosted.countDown(); - - // Condition 3: Wait for JFR flush. - // The default flush period is ~1 second. - readyToPostEventB.await(); - - // Load the second event type, EventB, AFTER the flush segment containing the two EventA events. - // A new metadata description will be constructed, and we verify that the StringPool reference added in the previous - // segment is still available for the EventB string pool reference to be resolved correctly. - emit(new EventB(), text); - - allEventsProcessed.await(); - } - } - - private static void emit(Event event, String text) { - if (event instanceof EventA a) { - a.text = text; - } - if (event instanceof EventB b) { - b.text = text; - } - event.commit(); - } -} +/* + * Copyright (c) 2026, 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.api.consumer.streaming; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test that it is possible to register new metadata in a new segment while retaining the string pool. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.streaming.TestMetadataReconstructionWithRetainedStringPool + */ +public class TestMetadataReconstructionWithRetainedStringPool { + + static final class EventA extends Event { + String text; + } + + static final class EventB extends Event { + String text; + } + + /// Minimum string length required to trigger StringPool usage. + /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. + private static final int STRING_POOL_MIN_LIMIT = 16; + + public static void main(String... args) throws InterruptedException { + var aEventsPosted = new CountDownLatch(1); + var readyToPostEventB = new CountDownLatch(1); + var allEventsProcessed = new CountDownLatch(1); + int expectedEvents = 3; + var eventsRemaining = new AtomicInteger(expectedEvents); + + // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding. + var text = "a".repeat(STRING_POOL_MIN_LIMIT + 1); + + try (var rs = new RecordingStream()) { + rs.onEvent(e -> { + String textValue = e.getValue("text"); + if (textValue == null) { + throw new RuntimeException("e.getValue(\"text\") returned null"); + } + int remaining = eventsRemaining.decrementAndGet(); + System.out.printf("Event #%d [%s]: text=%s%n", + expectedEvents - remaining, + e.getEventType().getName(), + textValue); + + if (remaining == 0) { + allEventsProcessed.countDown(); + } + }); + + rs.onFlush(() -> { + if (aEventsPosted.getCount() == 0) { + readyToPostEventB.countDown(); + } + }); + + rs.startAsync(); + + // Condition 2: Two distinct event types are required. + // First, load EventA as the initial event type and emit its first event. + // This first event looks into the StringPool pre-cache. Although the + // string length qualifies for pooling, because it isn't pre-cached, + // the first event encodes the string inline. + // The second event finds the string in the pre-cache and adds it to the + // pool. A constant pool ID to the pooled string is encoded in the event. + // + emit(new EventA(), text); + emit(new EventA(), text); + aEventsPosted.countDown(); + + // Condition 3: Wait for JFR flush. + // The default flush period is ~1 second. + readyToPostEventB.await(); + + // Load the second event type, EventB, AFTER the flush segment containing the two events of type EventA. + // A new metadata description will be constructed, and we verify that the StringPool added in the previous + // segment is still available for the EventB string pool reference to be resolved correctly. + emit(new EventB(), text); + + allEventsProcessed.await(); + } + } + + private static void emit(Event event, String text) { + if (event instanceof EventA a) { + a.text = text; + } + if (event instanceof EventB b) { + b.text = text; + } + event.commit(); + } +} From 137dfdd60989e6e44235ebe39aada9a32207ad82 Mon Sep 17 00:00:00 2001 From: mgronlun Date: Thu, 16 Apr 2026 15:52:19 +0200 Subject: [PATCH 3/4] streamline test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Rouél --- ...aReconstructionWithRetainedStringPool.java | 47 ++++++------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java index a9b4247a96423..8eaff117eecc5 100644 --- a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java @@ -38,28 +38,24 @@ * @run main/othervm jdk.jfr.api.consumer.streaming.TestMetadataReconstructionWithRetainedStringPool */ public class TestMetadataReconstructionWithRetainedStringPool { + /// Minimum string length required to trigger StringPool usage. + /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. + private static final int STRING_POOL_MIN_LIMIT = 16; + private static final String TEXT = "a".repeat(STRING_POOL_MIN_LIMIT + 1);; + private static final int EXPECTED_EVENTS = 3; static final class EventA extends Event { - String text; + String text = TEXT; } static final class EventB extends Event { - String text; + String text = TEXT; } - /// Minimum string length required to trigger StringPool usage. - /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. - private static final int STRING_POOL_MIN_LIMIT = 16; - public static void main(String... args) throws InterruptedException { var aEventsPosted = new CountDownLatch(1); var readyToPostEventB = new CountDownLatch(1); - var allEventsProcessed = new CountDownLatch(1); - int expectedEvents = 3; - var eventsRemaining = new AtomicInteger(expectedEvents); - - // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding. - var text = "a".repeat(STRING_POOL_MIN_LIMIT + 1); + var remaining = new CountDownLatch(EXPECTED_EVENTS); try (var rs = new RecordingStream()) { rs.onEvent(e -> { @@ -67,15 +63,11 @@ public static void main(String... args) throws InterruptedException { if (textValue == null) { throw new RuntimeException("e.getValue(\"text\") returned null"); } - int remaining = eventsRemaining.decrementAndGet(); + remaining.countDown(); System.out.printf("Event #%d [%s]: text=%s%n", - expectedEvents - remaining, + EXPECTED_EVENTS - remaining.getCount(), e.getEventType().getName(), textValue); - - if (remaining == 0) { - allEventsProcessed.countDown(); - } }); rs.onFlush(() -> { @@ -94,8 +86,8 @@ public static void main(String... args) throws InterruptedException { // The second event finds the string in the pre-cache and adds it to the // pool. A constant pool ID to the pooled string is encoded in the event. // - emit(new EventA(), text); - emit(new EventA(), text); + new EventA().commit(); + new EventA().commit(); aEventsPosted.countDown(); // Condition 3: Wait for JFR flush. @@ -105,19 +97,8 @@ public static void main(String... args) throws InterruptedException { // Load the second event type, EventB, AFTER the flush segment containing the two events of type EventA. // A new metadata description will be constructed, and we verify that the StringPool added in the previous // segment is still available for the EventB string pool reference to be resolved correctly. - emit(new EventB(), text); - - allEventsProcessed.await(); - } - } - - private static void emit(Event event, String text) { - if (event instanceof EventA a) { - a.text = text; - } - if (event instanceof EventB b) { - b.text = text; + new EventB().commit(); + remaining.await(); } - event.commit(); } } From 032cb8982a717cdca6f25fc57ba749ea4f853594 Mon Sep 17 00:00:00 2001 From: mgronlun Date: Thu, 16 Apr 2026 15:56:20 +0200 Subject: [PATCH 4/4] restore comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Rouél --- .../TestMetadataReconstructionWithRetainedStringPool.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java index 8eaff117eecc5..8bbadc6db290c 100644 --- a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java @@ -41,9 +41,11 @@ public class TestMetadataReconstructionWithRetainedStringPool { /// Minimum string length required to trigger StringPool usage. /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. private static final int STRING_POOL_MIN_LIMIT = 16; - private static final String TEXT = "a".repeat(STRING_POOL_MIN_LIMIT + 1);; private static final int EXPECTED_EVENTS = 3; + // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding. + private static final String TEXT = "a".repeat(STRING_POOL_MIN_LIMIT + 1);; + static final class EventA extends Event { String text = TEXT; }