From 58338fb2c7957b5278d9d8dda5fc0f953be6cec7 Mon Sep 17 00:00:00 2001 From: Mat Carter Date: Tue, 24 Feb 2026 12:44:59 -0800 Subject: [PATCH] AOT MXBean - passes tests --- ms-patches/aotmxbean.yml | 10 ++ src/hotspot/share/cds/metaspaceShared.cpp | 14 ++ src/hotspot/share/cds/metaspaceShared.hpp | 2 + src/hotspot/share/include/jvm.h | 12 ++ src/hotspot/share/prims/jvm.cpp | 62 ++++++++ .../classes/sun/management/VMManagement.java | 6 + .../sun/management/VMManagementImpl.java | 6 + .../native/libmanagement/VMManagementImpl.c | 27 ++++ .../sun/management/internal/AOTCacheImpl.java | 67 ++++++++ .../internal/PlatformMBeanProviderImpl.java | 36 +++++ .../jdk/management/AOTCacheMXBean.java | 77 +++++++++ .../appcds/aotCache/AOTCacheMXBeanTest.java | 146 ++++++++++++++++++ 12 files changed, 465 insertions(+) create mode 100644 ms-patches/aotmxbean.yml create mode 100644 src/jdk.management/share/classes/com/sun/management/internal/AOTCacheImpl.java create mode 100644 src/jdk.management/share/classes/jdk/management/AOTCacheMXBean.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheMXBeanTest.java diff --git a/ms-patches/aotmxbean.yml b/ms-patches/aotmxbean.yml new file mode 100644 index 00000000000..f59d9c63936 --- /dev/null +++ b/ms-patches/aotmxbean.yml @@ -0,0 +1,10 @@ +title: AOT +- work_item: 2497554 +- author: macarte +- owner: macarte +- contributors: + - macarte +- details: + - Moving work over from Leyden/premain that didn't make jdk25 + - AOTCache MX Bean +- release_note: Adding AOTCache MX Bean \ No newline at end of file diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index 4b623ec5c6d..2fbd09367f2 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -95,6 +95,7 @@ #include "runtime/vmOperations.hpp" #include "runtime/vmThread.hpp" #include "sanitizers/leak.hpp" +#include "services/management.hpp" #include "utilities/align.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/defaultStream.hpp" @@ -114,6 +115,7 @@ char* MetaspaceShared::_requested_base_address; Array* MetaspaceShared::_archived_method_handle_intrinsics = nullptr; bool MetaspaceShared::_use_optimized_module_handling = true; int volatile MetaspaceShared::_preimage_static_archive_dumped = 0; +jlong MetaspaceShared::_preimage_static_archive_recording_duration = 0; FileMapInfo* MetaspaceShared::_output_mapinfo = nullptr; // The CDS archive is divided into the following regions: @@ -937,6 +939,17 @@ bool MetaspaceShared::preimage_static_archive_dumped() { return Atomic::load_acquire(&_preimage_static_archive_dumped) == 1; } +jlong MetaspaceShared::get_preimage_static_archive_recording_duration() { + if (CDSConfig::is_dumping_preimage_static_archive()) { + if (_preimage_static_archive_recording_duration == 0) { + // The recording has not yet finished so return the current elapsed time. + return Management::ticks_to_ms(os::elapsed_counter()); + } + return _preimage_static_archive_recording_duration; + } + return 0; +} + void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS) { if (CDSConfig::is_dumping_preimage_static_archive()) { // When dumping to the AOT configuration file ensure this function is only executed once. @@ -945,6 +958,7 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS if (Atomic::cmpxchg(&_preimage_static_archive_dumped, 0, 1) != 0) { return; } + _preimage_static_archive_recording_duration = Management::ticks_to_ms(os::elapsed_counter()); } if (CDSConfig::is_dumping_classic_static_archive()) { diff --git a/src/hotspot/share/cds/metaspaceShared.hpp b/src/hotspot/share/cds/metaspaceShared.hpp index 687551e988a..1c6d0cafe95 100644 --- a/src/hotspot/share/cds/metaspaceShared.hpp +++ b/src/hotspot/share/cds/metaspaceShared.hpp @@ -60,6 +60,7 @@ class MetaspaceShared : AllStatic { static bool _use_optimized_module_handling; static Array* _archived_method_handle_intrinsics; static int volatile _preimage_static_archive_dumped; + static jlong _preimage_static_archive_recording_duration; static FileMapInfo* _output_mapinfo; public: @@ -113,6 +114,7 @@ class MetaspaceShared : AllStatic { static bool is_shared_static(void* p) NOT_CDS_RETURN_(false); static bool preimage_static_archive_dumped() NOT_CDS_RETURN_(false); + static jlong get_preimage_static_archive_recording_duration() NOT_CDS_RETURN_(0); static void unrecoverable_loading_error(const char* message = "unrecoverable error"); static void report_loading_error(const char* format, ...) ATTRIBUTE_PRINTF(1, 0); diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 73f60765a70..ed22a3203d5 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -87,6 +87,18 @@ JVM_InternString(JNIEnv *env, jstring str); /* * java.lang.System */ +JNIEXPORT jboolean JNICALL +JVM_AOTIsTraining(JNIEnv *env); + +JNIEXPORT jboolean JNICALL +JVM_AOTEndTraining(JNIEnv *env); + +JNIEXPORT jstring JNICALL +JVM_AOTGetMode(JNIEnv *env); + +JNIEXPORT jlong JNICALL +JVM_AOTGetRecordingDuration(JNIEnv *env); + JNIEXPORT jlong JNICALL JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index c6f1172ca08..03a3beb41c9 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -227,7 +227,69 @@ extern void trace_class_resolution(Klass* to_class) { } // java.lang.System ////////////////////////////////////////////////////////////////////// +JVM_LEAF(jboolean, JVM_AOTIsTraining(JNIEnv *env)) +#if INCLUDE_CDS + + if (!CDSConfig::is_dumping_preimage_static_archive()) { + // not a recording session + return JNI_FALSE; + } + + if (MetaspaceShared::preimage_static_archive_dumped()) { + // recording has already completed, so we're no longer "training" + return JNI_FALSE; + } + + // is training + return JNI_TRUE; +#else + return JNI_FALSE; +#endif // INCLUDE_CDS +JVM_END + +JVM_ENTRY(jboolean, JVM_AOTEndTraining(JNIEnv *env)) +#if INCLUDE_CDS + if (!CDSConfig::is_dumping_preimage_static_archive()) { + // not a recording session + return JNI_FALSE; + } + + if (MetaspaceShared::preimage_static_archive_dumped()) { + // recording has already completed, so we're no longer "training" + return JNI_FALSE; + } + + MetaspaceShared::preload_and_dump(THREAD); + if (!MetaspaceShared::preimage_static_archive_dumped()) { + // failed to complete recording and dumping the archive + return JNI_FALSE; + } + + // success + return JNI_TRUE; +#else + return JNI_FALSE; +#endif // INCLUDE_CDS +JVM_END + +JVM_ENTRY(jstring, JVM_AOTGetMode(JNIEnv *env)) + HandleMark hm(THREAD); +#if INCLUDE_CDS + const char* mode = AOTMode == nullptr ? "auto" : AOTMode; + Handle h = java_lang_String::create_from_platform_dependent_str(mode, CHECK_NULL); + return (jstring) JNIHandles::make_local(THREAD, h()); +#else + return nullptr; +#endif // INCLUDE_CDS +JVM_END +JVM_LEAF(jlong, JVM_AOTGetRecordingDuration(JNIEnv *env)) +#if INCLUDE_CDS + return MetaspaceShared::get_preimage_static_archive_recording_duration(); +#else + return 0; +#endif // INCLUDE_CDS +JVM_END JVM_LEAF(jlong, JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored)) return os::javaTimeMillis(); diff --git a/src/java.management/share/classes/sun/management/VMManagement.java b/src/java.management/share/classes/sun/management/VMManagement.java index f4445f0225a..6eaded7de7f 100644 --- a/src/java.management/share/classes/sun/management/VMManagement.java +++ b/src/java.management/share/classes/sun/management/VMManagement.java @@ -48,6 +48,12 @@ public interface VMManagement { public boolean isGcNotificationSupported(); public boolean isRemoteDiagnosticCommandsSupported(); + // AOT Subsytem + public String getAOTMode(); + public boolean isAOTRecording(); + public long getAOTRecordingDuration(); + public boolean endAOTRecording(); + // Class Loading Subsystem public long getTotalClassCount(); public int getLoadedClassCount(); diff --git a/src/java.management/share/classes/sun/management/VMManagementImpl.java b/src/java.management/share/classes/sun/management/VMManagementImpl.java index 041f09547d2..24c44601e8d 100644 --- a/src/java.management/share/classes/sun/management/VMManagementImpl.java +++ b/src/java.management/share/classes/sun/management/VMManagementImpl.java @@ -117,6 +117,12 @@ public boolean isRemoteDiagnosticCommandsSupported() { public native boolean isThreadCpuTimeEnabled(); public native boolean isThreadAllocatedMemoryEnabled(); + // AOT Subsystem + public native String getAOTMode(); + public native boolean isAOTRecording(); + public native long getAOTRecordingDuration(); + public native boolean endAOTRecording(); + // Class Loading Subsystem public int getLoadedClassCount() { long count = getTotalClassCount() - getUnloadedClassCount(); diff --git a/src/java.management/share/native/libmanagement/VMManagementImpl.c b/src/java.management/share/native/libmanagement/VMManagementImpl.c index f1a566676dc..698025dab0c 100644 --- a/src/java.management/share/native/libmanagement/VMManagementImpl.c +++ b/src/java.management/share/native/libmanagement/VMManagementImpl.c @@ -100,6 +100,33 @@ Java_sun_management_VMManagementImpl_getVmArguments0 { return JVM_GetVmArguments(env); } +JNIEXPORT jstring JNICALL +Java_sun_management_VMManagementImpl_getAOTMode + (JNIEnv *env, jobject dummy) +{ + return JVM_AOTGetMode(env); +} + +JNIEXPORT jboolean JNICALL +Java_sun_management_VMManagementImpl_isAOTRecording + (JNIEnv *env, jobject dummy) +{ + return JVM_AOTIsTraining(env); +} + +JNIEXPORT jlong JNICALL +Java_sun_management_VMManagementImpl_getAOTRecordingDuration + (JNIEnv *env, jobject dummy) +{ + return JVM_AOTGetRecordingDuration(env); +} + +JNIEXPORT jboolean JNICALL +Java_sun_management_VMManagementImpl_endAOTRecording + (JNIEnv *env, jobject dummy) +{ + return JVM_AOTEndTraining(env); +} JNIEXPORT jlong JNICALL Java_sun_management_VMManagementImpl_getTotalClassCount diff --git a/src/jdk.management/share/classes/com/sun/management/internal/AOTCacheImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/AOTCacheImpl.java new file mode 100644 index 00000000000..525637b9a2a --- /dev/null +++ b/src/jdk.management/share/classes/com/sun/management/internal/AOTCacheImpl.java @@ -0,0 +1,67 @@ +/* + * 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. 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 com.sun.management.internal; + +import javax.management.ObjectName; +import jdk.management.AOTCacheMXBean; +import sun.management.Util; +import sun.management.VMManagement; + +/** + * Implementation class for the AOT Cache subsystem. + * + * ManagementFactory.getRuntimeMXBean() returns an instance + * of this class. + */ +public class AOTCacheImpl implements AOTCacheMXBean { + + private final VMManagement jvm; + /** + * Constructor of AOTCacheImpl class. + */ + AOTCacheImpl(VMManagement vm) { + this.jvm = vm; + } + + public String getMode() { + return jvm.getAOTMode(); + } + + public boolean isRecording() { + return jvm.isAOTRecording(); + } + + public long getRecordingDuration(){ + return jvm.getAOTRecordingDuration(); + } + + public boolean endRecording(){ + return jvm.endAOTRecording(); + } + + public ObjectName getObjectName() { + return Util.newObjectName("jdk.management:type=AOTCache"); + } +} \ No newline at end of file diff --git a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java index 3a64fe6b858..667dbb65ded 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.management.DynamicMBean; +import jdk.management.AOTCacheMXBean; import jdk.management.VirtualThreadSchedulerMXBean; import sun.management.ManagementFactoryHelper; import sun.management.spi.PlatformMBeanProvider; @@ -159,6 +160,41 @@ public synchronized Map nameToMBeanMa } }); + /** + * AOTCacheMXBean. + */ + initMBeanList.add(new PlatformComponent() { + private final Set> mbeanInterfaces = + Set.of(AOTCacheMXBean.class); + private final Set mbeanInterfaceNames = + Set.of(AOTCacheMXBean.class.getName()); + private AOTCacheMXBean impl; + + @Override + public Set> mbeanInterfaces() { + return mbeanInterfaces; + } + + @Override + public Set mbeanInterfaceNames() { + return mbeanInterfaceNames; + } + + @Override + public String getObjectNamePattern() { + return "jdk.management:type=AOTCache"; + } + + @Override + public Map nameToMBeanMap() { + AOTCacheMXBean impl = this.impl; + if (impl == null) { + this.impl = impl = new AOTCacheImpl(ManagementFactoryHelper.getVMManagement()); + } + return Map.of("jdk.management:type=AOTCache", impl); + } + }); + /** * VirtualThreadSchedulerMXBean. */ diff --git a/src/jdk.management/share/classes/jdk/management/AOTCacheMXBean.java b/src/jdk.management/share/classes/jdk/management/AOTCacheMXBean.java new file mode 100644 index 00000000000..67e5dec2e9e --- /dev/null +++ b/src/jdk.management/share/classes/jdk/management/AOTCacheMXBean.java @@ -0,0 +1,77 @@ +/* + * 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. 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.management; + +import java.lang.management.ManagementFactory; +import java.lang.management.PlatformManagedObject; +import java.util.concurrent.ForkJoinPool; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +/** + * Management interface for the JDK's AOT system. + * + *

{@code AOTCacheMXBean} supports inspection of the current AOT mode, as well as monitoring + * the current recording length. It also supports dynamically ending the current recording. + * + *

The management interface is registered with the platform {@link MBeanServer + * MBeanServer}. The {@link ObjectName ObjectName} that uniquely identifies the management + * interface within the {@code MBeanServer} is: "jdk.management:type=AOTCache". + * + *

Direct access to the MXBean interface can be obtained with + * {@link ManagementFactory#getPlatformMXBean(Class)}. + * + * @since 25 + */ +public interface AOTCacheMXBean extends PlatformManagedObject { + /** + * Returns the string representing the current AOT mode of + * operation. + * + * @return the string representing the current AOT mode. + */ + public String getMode(); + + /** + * Tests if a recording is in progress. + * + * @return {@code true} if a recording is in progress; {@code false} otherwise. + */ + public boolean isRecording(); + + /** + * If a recording is in progress or has been completed, then returns the duration in milliseconds + * + * @return duration of the recording in milliseconds. + */ + public long getRecordingDuration(); + + /** + * If a recording is in progress, then ends the recording. + * + * @return {@code true} if a recording was stopped; {@code false} otherwise. + */ + public boolean endRecording(); +} \ No newline at end of file diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheMXBeanTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheMXBeanTest.java new file mode 100644 index 00000000000..d86bf7f0bc6 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheMXBeanTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2025, Microsoft, Inc. 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. + * + */ + + +/* + * @test + * @summary Sanity test for AOTCache MXBean + * @requires vm.cds.write.archived.java.heap + * @library /test/jdk/lib/testlibrary /test/lib + * /test/hotspot/jtreg/runtime/cds/appcds/aotCache/test-classes + * @build AOTCacheMXBeanTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTCacheMXBeanApp + * @run driver AOTCacheMXBeanTest + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import java.lang.management.ManagementFactory; +import jdk.management.AOTCacheMXBean; + +public class AOTCacheMXBeanTest { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "AOTCacheMXBeanApp"; + public static void main(String[] args) throws Exception { + Tester tester = new Tester(); + tester.runAOTWorkflow(); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:cds+class=trace", + "--add-modules=jdk.management" + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, runMode.name() + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + var name = runMode.name(); + if (runMode.isApplicationExecuted()) { + out.shouldContain("Hello Leyden " + name); + if(runMode == RunMode.TRAINING) { + out.shouldContain("AOTMode = record"); + out.shouldContain("Confirmed is recording"); + out.shouldContain("Confirmed recording duration > 0"); + out.shouldContain("Stopped recording successfully after an additional 10ms"); + out.shouldContain("Last recording duration > than previous duration"); + out.shouldContain("Confirmed recording stopped"); + out.shouldContain("Confirmed recording duration has not changed after 10ms"); + } else if (runMode == RunMode.ASSEMBLY) { + out.shouldNotContain("Hello Leyden "); + } else if (runMode == RunMode.PRODUCTION) { + out.shouldContain("AOTMode = on"); + out.shouldContain("Confirmed is not recording"); + out.shouldContain("Confirmed recording duration == 0"); + } + out.shouldNotContain("Thread interrupted"); + out.shouldNotContain("Failed to stop recording"); + } + } + } +} + +class AOTCacheMXBeanApp { + public static void main(String[] args) { + System.out.println("Hello Leyden " + args[0]); + var aotBean = ManagementFactory.getPlatformMXBean(AOTCacheMXBean.class); + if (aotBean == null) { + System.out.println("AOTCacheMXBean is not available"); + return; + } + System.out.println("AOTMode = " + aotBean.getMode()); + if (aotBean.isRecording()) { + try { + System.out.println("Confirmed is recording"); + var initialDuration = aotBean.getRecordingDuration(); + System.out.println("Confirmed recording duration > 0"); + Thread.sleep(10); + if (aotBean.endRecording()) { + System.out.println("Stopped recording successfully after an additional 10ms"); + if (!aotBean.isRecording()) { + System.out.println("Confirmed recording stopped"); + } + var recordingDuration = aotBean.getRecordingDuration(); + if (recordingDuration > initialDuration) { + System.out.println("Last recording duration > than previous duration"); + } + Thread.sleep(10); + var lastDuration = aotBean.getRecordingDuration(); + if (lastDuration == recordingDuration) { + System.out.println("Confirmed recording duration has not changed after 10ms"); + } + } else { + System.out.println("Failed to stop recording"); + } + } catch (InterruptedException e) { + System.out.println("Thread interrupted"); + } + } else { + System.out.println("Confirmed is not recording"); + var recordingDuration = aotBean.getRecordingDuration(); + if (recordingDuration == 0) { + System.out.println("Confirmed recording duration == 0"); + } + } + } +} \ No newline at end of file