diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp index 163800e9abf9..76ef4abf76f6 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp @@ -193,10 +193,8 @@ void StringDedup::Processor::run(JavaThread* thread) { void StringDedup::Processor::log_statistics() { _total_stat.add(&_cur_stat); Stat::log_summary(&_cur_stat, &_total_stat); - if (log_is_enabled(Debug, stringdedup)) { - _cur_stat.log_statistics(false); - _total_stat.log_statistics(true); - Table::log_statistics(); - } + _cur_stat.emit_statistics(false /* total */); + _total_stat.emit_statistics(true /* total */); + Table::log_statistics(); _cur_stat = Stat{}; } diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp index 9ce3d0f91646..e67483d64174 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "gc/shared/stringdedup/stringDedupStat.hpp" +#include "jfr/jfrEvents.hpp" #include "logging/log.hpp" #include "utilities/globalDefinitions.hpp" @@ -92,13 +93,6 @@ static double strdedup_elapsed_param_ms(Tickspan t) { } void StringDedup::Stat::log_summary(const Stat* last_stat, const Stat* total_stat) { - double total_deduped_bytes_percent = 0.0; - - if (total_stat->_new_bytes > 0) { - // Avoid division by zero - total_deduped_bytes_percent = percent_of(total_stat->_deduped_bytes, total_stat->_new_bytes); - } - log_info(stringdedup)( "Concurrent String Deduplication " "%zu (inspected), " @@ -110,7 +104,7 @@ void StringDedup::Stat::log_summary(const Stat* last_stat, const Stat* total_sta last_stat->_inspected, last_stat->_new, STRDEDUP_BYTES_PARAM(last_stat->_new_bytes), last_stat->_deduped, STRDEDUP_BYTES_PARAM(last_stat->_deduped_bytes), - total_deduped_bytes_percent, + percent_of(total_stat->_deduped_bytes, total_stat->_new_bytes), STRDEDUP_BYTES_PARAM(total_stat->_deduped_bytes), STRDEDUP_BYTES_PARAM(total_stat->_new_bytes), strdedup_elapsed_param_ms(last_stat->_process_elapsed), strdedup_elapsed_param_ms(last_stat->_active_elapsed)); @@ -213,7 +207,7 @@ void StringDedup::Stat::log_times(const char* prefix) const { } } -void StringDedup::Stat::log_statistics(bool total) const { +void StringDedup::Stat::log_statistics() const { double known_percent = percent_of(_known, _inspected); double known_shared_percent = percent_of(_known_shared, _inspected); double new_percent = percent_of(_new, _inspected); @@ -221,7 +215,6 @@ void StringDedup::Stat::log_statistics(bool total) const { double deduped_bytes_percent = percent_of(_deduped_bytes, _new_bytes); double replaced_percent = percent_of(_replaced, _new); double deleted_percent = percent_of(_deleted, _new); - log_times(total ? "Total" : "Last"); log_debug(stringdedup)(" Inspected: %12zu", _inspected); log_debug(stringdedup)(" Known: %12zu(%5.1f%%)", _known, known_percent); log_debug(stringdedup)(" Shared: %12zu(%5.1f%%)", _known_shared, known_shared_percent); @@ -234,3 +227,40 @@ void StringDedup::Stat::log_statistics(bool total) const { log_debug(stringdedup)(" Skipped: %zu (dead), %zu (incomplete), %zu (shared)", _skipped_dead, _skipped_incomplete, _skipped_shared); } + +void StringDedup::Stat::emit_statistics(bool total) const { + if (log_is_enabled(Debug, stringdedup)) { + log_times(total ? "Total" : "Last"); + log_statistics(); + } + + if (total) { + // Send only JFR events about the last stats + return; + } + + EventStringDeduplication e; + if (e.should_commit()) { + e.set_starttime(_active_start); + Ticks active_end = _active_start; + active_end += _active_elapsed; + e.set_endtime(active_end); + + e.set_inspected(_inspected); + e.set_known(_known); + e.set_shared(_known_shared); + e.set_newStrings(_new); + e.set_newSize(_new_bytes); + e.set_replaced(_replaced); + e.set_deleted(_deleted); + e.set_deduplicated(_deduped); + e.set_deduplicatedSize(_deduped_bytes); + e.set_skippedDead(_skipped_dead); + e.set_skippedIncomplete(_skipped_incomplete); + e.set_skippedShared(_skipped_shared); + e.set_processing(_process_elapsed); + e.set_tableResize(_resize_table_elapsed); + e.set_tableCleanup(_cleanup_table_elapsed); + e.commit(); + } +} diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp index db753af3be5b..fb864ab34ab6 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp @@ -71,6 +71,7 @@ class StringDedup::Stat { void report_phase_end(const char* phase, Tickspan* elapsed); void log_times(const char* prefix) const; + void log_statistics() const; public: Stat(); @@ -148,7 +149,7 @@ class StringDedup::Stat { void report_active_end(); void add(const Stat* const stat); - void log_statistics(bool total) const; + void emit_statistics(bool total) const; static void log_summary(const Stat* last_stat, const Stat* total_stat); }; diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp index ac8fc0759caf..58ab7584d50d 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp @@ -724,6 +724,10 @@ void StringDedup::Table::verify() { } void StringDedup::Table::log_statistics() { + if (!log_is_enabled(Debug, stringdedup)) { + return; + } + size_t dead_count; int dead_state; { diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 80ea101a7c96..ac34050d9341 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1178,6 +1178,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index ee03f66162e5..210f0296b9a9 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -450,6 +450,11 @@ true + + true + 0 ms + + true diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index bdd148121c14..b51384503b4f 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -450,6 +450,11 @@ true + + true + 0 ms + + true diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java new file mode 100644 index 000000000000..3221e76b4d29 --- /dev/null +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java @@ -0,0 +1,172 @@ +/* + * 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.gc.detailed; + +import java.lang.reflect.Field; +import java.lang.management.ManagementFactory; +import java.lang.management.GarbageCollectorMXBean; +import java.util.List; +import java.util.ArrayList; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; +import jdk.test.whitebox.WhiteBox; + +/** + * @test id=Serial + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Serial + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseSerialGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=Parallel + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Parallel + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseParallelGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=G1 + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.G1 + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseG1GC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=Z + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Z + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseZGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=Shenandoah + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Shenandoah + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseShenandoahGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +public class TestStringDeduplicationEvent { + private static Field valueField; + + static { + try { + valueField = String.class.getDeclaredField("value"); + valueField.setAccessible(true); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + public static void main(String[] args) throws Exception { + boolean zgc = isZgc(); + + try (RecordingStream recording = new RecordingStream()) { + recording.enable(EventNames.StringDeduplication); + recording.onEvent(EventNames.StringDeduplication, e -> recording.close()); + recording.startAsync(); + + String base = TestStringDeduplicationEvent.class.getSimpleName(); + String duplicate = new StringBuilder(base).toString(); + assert(getValue(base) != getValue(duplicate)); + + if (zgc) { + // ZGC only triggers string deduplications from major collections + WhiteBox.getWhiteBox().fullGC(); + } else { + WhiteBox.getWhiteBox().youngGC(); + } + + recording.awaitTermination(); + } + } + + private static Object getValue(String string) { + try { + return valueField.get(string); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static boolean isZgc() { + List gcs = ManagementFactory.getGarbageCollectorMXBeans(); + return gcs.getFirst().getName().contains("ZGC"); + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index ba53f9598df5..bc595d7aba2b 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -154,6 +154,7 @@ public class EventNames { public static final String GCLocker = PREFIX + "GCLocker"; public static final String SystemGC = PREFIX + "SystemGC"; public static final String GCCPUTime = PREFIX + "GCCPUTime"; + public static final String StringDeduplication = PREFIX + "StringDeduplication"; // Compiler public static final String Compilation = PREFIX + "Compilation";