Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/hotspot/share/cds/aotMetaspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ intx AOTMetaspace::_relocation_delta;
char* AOTMetaspace::_requested_base_address;
Array<Method*>* AOTMetaspace::_archived_method_handle_intrinsics = nullptr;
bool AOTMetaspace::_use_optimized_module_handling = true;
int volatile AOTMetaspace::_preimage_static_archive_dumped = 0;

// The CDS archive is divided into the following regions:
// rw - read-write metadata
Expand Down Expand Up @@ -957,7 +958,20 @@ void AOTMetaspace::exercise_runtime_cds_code(TRAPS) {
CDSProtectionDomain::to_file_URL("dummy.jar", Handle(), CHECK);
}

bool AOTMetaspace::is_recording_preimage_static_archive() {
if (CDSConfig::is_dumping_preimage_static_archive()) {
return _preimage_static_archive_dumped == 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use an acquiring load here? i.e. can the read here and the cmpxchg below happen in different threads?
n.b. I'm not just thinking about the behaviour when this patch makes the DCmd available but also what happens when we supplement it with the MXBean interface to end recordings.

Copy link
Contributor Author

@macarte macarte Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's okay, as ending the recording is controlled by the cmpxchg, even if two threads think the recording is still going on, only one call to end the recording will work, and if the threads both check whether the recording has completed they will both see that it has (regardless of which thread 'won')

}
return false;
}

void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS) {
if (CDSConfig::is_dumping_preimage_static_archive()) {
if (AtomicAccess::cmpxchg(&_preimage_static_archive_dumped, 0, 1) != 0) {
return;
}
}

if (CDSConfig::is_dumping_classic_static_archive()) {
// We are running with -Xshare:dump
load_classes(CHECK);
Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/cds/aotMetaspace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class AOTMetaspace : AllStatic {
static char* _requested_base_address;
static bool _use_optimized_module_handling;
static Array<Method*>* _archived_method_handle_intrinsics;
static int volatile _preimage_static_archive_dumped;

public:
enum {
Expand Down Expand Up @@ -113,6 +114,8 @@ class AOTMetaspace : AllStatic {
// inside the metaspace of the dynamic static CDS archive
static bool in_aot_cache_dynamic_region(void* p) NOT_CDS_RETURN_(false);

static bool is_recording_preimage_static_archive() NOT_CDS_RETURN_(false);

static void unrecoverable_loading_error(const char* message = "unrecoverable error");
static void report_loading_error(const char* format, ...) ATTRIBUTE_PRINTF(1, 0);
static void unrecoverable_writing_error(const char* message = nullptr);
Expand Down
24 changes: 24 additions & 0 deletions src/hotspot/share/services/diagnosticCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*
*/

#include "cds/aotMetaspace.hpp"
#include "cds/cds_globals.hpp"
#include "cds/cdsConfig.hpp"
#include "classfile/classLoaderDataGraph.hpp"
Expand Down Expand Up @@ -165,6 +166,7 @@ void DCmd::register_dcmds(){

#if INCLUDE_CDS
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<DumpSharedArchiveDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<AOTEndRecordingDCmd>(full_export, true, false));
#endif // INCLUDE_CDS

DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<NMTDCmd>(full_export, true, false));
Expand Down Expand Up @@ -986,6 +988,28 @@ void ClassesDCmd::execute(DCmdSource source, TRAPS) {
VMThread::execute(&vmop);
}

#if INCLUDE_CDS
void AOTEndRecordingDCmd::execute(DCmdSource source, TRAPS) {
if (!CDSConfig::is_dumping_preimage_static_archive()) {
output()->print_cr("Error! Not a recording run");
return;
}

if (!AOTMetaspace::is_recording_preimage_static_archive()) {
output()->print_cr("Error! Not recording");
return;
}

AOTMetaspace::dump_static_archive(THREAD);
if (!AOTMetaspace::is_recording_preimage_static_archive()) {
output()->print_cr("Recording ended successfully");
return;
}

output()->print_cr("Error! Failed to end recording");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an error recorded or anything further that could be included for the error case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is not, currently, the aotMetaspace method throws an exception if something goes wrong. I added this line in case the flow in aotMetaspace changes. The logic here is that if we asked the recording to stop, but it's still recording then we report to the user that 'we failed to stop the recording'

}
#endif // INCLUDE_CDS

#if INCLUDE_CDS
#define DEFAULT_CDS_ARCHIVE_FILENAME "java_pid%p_<subcmd>.jsa"

Expand Down
15 changes: 15 additions & 0 deletions src/hotspot/share/services/diagnosticCommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,21 @@ class ClassHierarchyDCmd : public DCmdWithParser {
virtual void execute(DCmdSource source, TRAPS);
};

#if INCLUDE_CDS
class AOTEndRecordingDCmd : public DCmd {
public:
AOTEndRecordingDCmd(outputStream* output, bool heap) : DCmd(output, heap) { }
static const char* name() { return "AOT.end_recording"; }
static const char* description() {
return "End AOT recording.";
}
static const char* impact() {
return "Medium: Pause time depends on number of loaded classes";
}
virtual void execute(DCmdSource source, TRAPS);
};
#endif // INCLUDE_CDS

#if INCLUDE_CDS
class DumpSharedArchiveDCmd: public DCmdWithParser {
protected:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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
* @requires vm.cds.supports.aot.class.linking
* @requires vm.cds.write.archived.java.heap
* @summary Sanity test for Jcmd AOT.end_recording command
* @library /test/lib
* @build JcmdAOTEndRecordingTest
* @run driver JcmdAOTEndRecordingTest
*/

import jdk.test.lib.JDKToolLauncher;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.dcmd.PidJcmdExecutor;
import jdk.test.lib.process.OutputAnalyzer;
import java.io.IOException;

public class JcmdAOTEndRecordingTest {
public static void main(String[] args) throws Exception {
LingeredApp theApp = null;
try {
theApp = new LingeredApp();
theApp.setUseDefaultClasspath(false);
LingeredApp.startApp(theApp);
long pid = theApp.getPid();

JDKToolLauncher jcmd = JDKToolLauncher.createUsingTestJDK("jcmd");
jcmd.addToolArg(String.valueOf(pid));
jcmd.addToolArg("AOT.end_recording");

try {
OutputAnalyzer output = ProcessTools.executeProcess(jcmd.getCommand());
output.shouldContain("Error! Not a recording run");
} catch (Exception e) {
throw new RuntimeException("Test failed: " + e);
}
}
catch (IOException e) {
throw new RuntimeException("Test failed: " + e);
}
finally {
LingeredApp.stopApp(theApp);
}
}
}