8261455: Automatically generate the CDS archive if necessary

Reviewed-by: iklam, ccheung
This commit is contained in:
Yumin Qi 2022-01-13 00:23:05 +00:00
parent d70545d710
commit 1228b2f1f8
13 changed files with 1074 additions and 171 deletions

View File

@ -187,7 +187,7 @@ public:
};
void DynamicArchiveBuilder::init_header() {
FileMapInfo* mapinfo = new FileMapInfo(false);
FileMapInfo* mapinfo = new FileMapInfo(_archive_name, false);
assert(FileMapInfo::dynamic_info() == mapinfo, "must be");
FileMapInfo* base_info = FileMapInfo::current_info();
// header only be available after populate_header
@ -327,7 +327,7 @@ void DynamicArchiveBuilder::write_archive(char* serialized_data) {
FileMapInfo* dynamic_info = FileMapInfo::dynamic_info();
assert(dynamic_info != NULL, "Sanity");
dynamic_info->open_for_write(_archive_name);
dynamic_info->open_for_write();
ArchiveBuilder::write_archive(dynamic_info, NULL, NULL, NULL, NULL);
address base = _requested_dynamic_archive_bottom;

View File

@ -166,8 +166,9 @@ template <int N> static void get_header_version(char (&header_version) [N]) {
assert(header_version[JVM_IDENT_MAX-1] == 0, "must be");
}
FileMapInfo::FileMapInfo(bool is_static) {
FileMapInfo::FileMapInfo(const char* full_path, bool is_static) {
memset((void*)this, 0, sizeof(FileMapInfo));
_full_path = full_path;
_is_static = is_static;
if (_is_static) {
assert(_current_info == NULL, "must be singleton"); // not thread safe
@ -188,6 +189,9 @@ FileMapInfo::~FileMapInfo() {
assert(_dynamic_archive_info == this, "must be singleton"); // not thread safe
_dynamic_archive_info = NULL;
}
if (_file_open) {
os::close(_fd);
}
}
void FileMapInfo::populate_header(size_t core_region_alignment) {
@ -1049,11 +1053,20 @@ void FileMapInfo::validate_non_existent_class_paths() {
class FileHeaderHelper {
int _fd;
bool _is_valid;
bool _is_static;
GenericCDSFileMapHeader* _header;
const char* _archive_name;
const char* _base_archive_name;
public:
FileHeaderHelper() : _fd(-1), _is_valid(false), _header(nullptr), _base_archive_name(nullptr) {}
FileHeaderHelper(const char* archive_name, bool is_static) {
_fd = -1;
_is_valid = false;
_header = nullptr;
_base_archive_name = nullptr;
_archive_name = archive_name;
_is_static = is_static;
}
~FileHeaderHelper() {
if (_fd != -1) {
@ -1061,11 +1074,11 @@ public:
}
}
bool initialize(const char* archive_name) {
log_info(cds)("Opening shared archive: %s", archive_name);
_fd = os::open(archive_name, O_RDONLY | O_BINARY, 0);
bool initialize() {
assert(_archive_name != nullptr, "Archive name is NULL");
_fd = os::open(_archive_name, O_RDONLY | O_BINARY, 0);
if (_fd < 0) {
FileMapInfo::fail_continue("Specified shared archive not found (%s)", archive_name);
FileMapInfo::fail_continue("Specified shared archive not found (%s)", _archive_name);
return false;
}
return initialize(_fd);
@ -1073,9 +1086,8 @@ public:
// for an already opened file, do not set _fd
bool initialize(int fd) {
assert(fd != -1, "Archive should be opened");
assert(_archive_name != nullptr, "Archive name is NULL");
assert(fd != -1, "Archive must be opened already");
// First read the generic header so we know the exact size of the actual header.
GenericCDSFileMapHeader gen_header;
size_t size = sizeof(GenericCDSFileMapHeader);
@ -1098,6 +1110,11 @@ public:
return false;
}
if (gen_header._version != CURRENT_CDS_ARCHIVE_VERSION) {
FileMapInfo::fail_continue("The shared archive file version %d does not match the required version %d",
gen_header._version, CURRENT_CDS_ARCHIVE_VERSION);
}
size_t filelen = os::lseek(fd, 0, SEEK_END);
if (gen_header._header_size >= filelen) {
FileMapInfo::fail_continue("Archive file header larger than archive file");
@ -1203,28 +1220,6 @@ public:
}
};
bool FileMapInfo::check_archive(const char* archive_name, bool is_static) {
FileHeaderHelper file_helper;
if (!file_helper.initialize(archive_name)) {
// Any errors are reported by fail_continue().
return false;
}
GenericCDSFileMapHeader* header = file_helper.get_generic_file_header();
if (is_static) {
if (header->_magic != CDS_ARCHIVE_MAGIC) {
fail_continue("Not a base shared archive: %s", archive_name);
return false;
}
} else {
if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
fail_continue("Not a top shared archive: %s", archive_name);
return false;
}
}
return true;
}
// Return value:
// false:
// <archive_name> is not a valid archive. *base_archive_name is set to null.
@ -1234,15 +1229,18 @@ bool FileMapInfo::check_archive(const char* archive_name, bool is_static) {
// <archive_name> is a valid dynamic archive.
bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name,
char** base_archive_name) {
FileHeaderHelper file_helper;
FileHeaderHelper file_helper(archive_name, false);
*base_archive_name = NULL;
if (!file_helper.initialize(archive_name)) {
if (!file_helper.initialize()) {
return false;
}
GenericCDSFileMapHeader* header = file_helper.get_generic_file_header();
if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
assert(header->_magic == CDS_ARCHIVE_MAGIC, "must be");
if (AutoCreateSharedArchive) {
log_warning(cds)("AutoCreateSharedArchive is ignored because %s is a static archive", archive_name);
}
return true;
}
@ -1259,20 +1257,24 @@ bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name,
// Read the FileMapInfo information from the file.
bool FileMapInfo::init_from_file(int fd) {
FileHeaderHelper file_helper;
FileHeaderHelper file_helper(_full_path, _is_static);
if (!file_helper.initialize(fd)) {
fail_continue("Unable to read the file header.");
return false;
}
GenericCDSFileMapHeader* gen_header = file_helper.get_generic_file_header();
unsigned int expected_magic = is_static() ? CDS_ARCHIVE_MAGIC : CDS_DYNAMIC_ARCHIVE_MAGIC;
if (gen_header->_magic != expected_magic) {
log_info(cds)("_magic expected: 0x%08x", expected_magic);
log_info(cds)(" actual: 0x%08x", gen_header->_magic);
FileMapInfo::fail_continue("The shared archive file has a bad magic number.");
if (_is_static) {
if (gen_header->_magic != CDS_ARCHIVE_MAGIC) {
FileMapInfo::fail_continue("Not a base shared archive: %s", _full_path);
return false;
}
} else {
if (gen_header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
FileMapInfo::fail_continue("Not a top shared archive: %s", _full_path);
return false;
}
}
_header = (FileMapHeader*)os::malloc(gen_header->_header_size, mtInternal);
lseek(fd, 0, SEEK_SET); // reset to begin of the archive
@ -1348,11 +1350,6 @@ bool FileMapInfo::open_for_read() {
if (_file_open) {
return true;
}
if (is_static()) {
_full_path = Arguments::GetSharedArchivePath();
} else {
_full_path = Arguments::GetSharedDynamicArchivePath();
}
log_info(cds)("trying to map %s", _full_path);
int fd = os::open(_full_path, O_RDONLY | O_BINARY, 0);
if (fd < 0) {
@ -1374,12 +1371,7 @@ bool FileMapInfo::open_for_read() {
// Write the FileMapInfo information to the file.
void FileMapInfo::open_for_write(const char* path) {
if (path == NULL) {
_full_path = Arguments::GetSharedArchivePath();
} else {
_full_path = path;
}
void FileMapInfo::open_for_write() {
LogMessage(cds) msg;
if (msg.is_info()) {
msg.info("Dumping shared data to file: ");
@ -2364,15 +2356,20 @@ bool FileMapInfo::initialize() {
return false;
}
if (!open_for_read()) {
if (!open_for_read() || !init_from_file(_fd) || !validate_header()) {
if (_is_static) {
FileMapInfo::fail_continue("Initialize static archive failed.");
return false;
} else {
FileMapInfo::fail_continue("Initialize dynamic archive failed.");
if (AutoCreateSharedArchive) {
DynamicDumpSharedSpaces = true;
ArchiveClassesAtExit = Arguments::GetSharedDynamicArchivePath();
}
return false;
}
if (!init_from_file(_fd)) {
return false;
}
if (!validate_header()) {
return false;
}
return true;
}

View File

@ -356,7 +356,6 @@ private:
public:
static bool get_base_archive_name_from_header(const char* archive_name,
char** base_archive_name);
static bool check_archive(const char* archive_name, bool is_static);
static SharedPathTable shared_path_table() {
return _shared_path_table;
}
@ -370,7 +369,7 @@ public:
void log_paths(const char* msg, int start_idx, int end_idx);
FileMapInfo(bool is_static);
FileMapInfo(const char* full_apth, bool is_static);
~FileMapInfo();
// Accessors
@ -441,7 +440,7 @@ public:
// File manipulation.
bool initialize() NOT_CDS_RETURN_(false);
bool open_for_read();
void open_for_write(const char* path = NULL);
void open_for_write();
void write_header();
void write_region(int region, char* base, size_t size,
bool read_only, bool allow_exec);

View File

@ -556,7 +556,9 @@ void VM_PopulateDumpSharedSpace::doit() {
builder.relocate_to_requested();
// Write the archive file
FileMapInfo* mapinfo = new FileMapInfo(true);
const char* static_archive = Arguments::GetSharedArchivePath();
assert(static_archive != nullptr, "SharedArchiveFile not set?");
FileMapInfo* mapinfo = new FileMapInfo(static_archive, true);
mapinfo->populate_header(MetaspaceShared::core_region_alignment());
mapinfo->set_serialized_data(serialized_data);
mapinfo->set_cloned_vtables(cloned_vtables);
@ -946,12 +948,17 @@ void MetaspaceShared::initialize_runtime_shared_and_meta_spaces() {
_requested_base_address = static_mapinfo->requested_base_address();
if (dynamic_mapped) {
FileMapInfo::set_shared_path_table(dynamic_mapinfo);
// turn AutoCreateSharedArchive off if successfully mapped
AutoCreateSharedArchive = false;
} else {
FileMapInfo::set_shared_path_table(static_mapinfo);
}
} else {
set_shared_metaspace_range(NULL, NULL, NULL);
UseSharedSpaces = false;
// The base archive cannot be mapped. We cannot dump the dynamic shared archive.
AutoCreateSharedArchive = false;
DynamicDumpSharedSpaces = false;
FileMapInfo::fail_continue("Unable to map shared spaces");
if (PrintSharedArchiveAndExit) {
vm_exit_during_initialization("Unable to use shared archive.");
@ -967,7 +974,9 @@ void MetaspaceShared::initialize_runtime_shared_and_meta_spaces() {
}
FileMapInfo* MetaspaceShared::open_static_archive() {
FileMapInfo* mapinfo = new FileMapInfo(true);
const char* static_archive = Arguments::GetSharedArchivePath();
assert(static_archive != nullptr, "SharedArchivePath is NULL");
FileMapInfo* mapinfo = new FileMapInfo(static_archive, true);
if (!mapinfo->initialize()) {
delete(mapinfo);
return NULL;
@ -979,11 +988,12 @@ FileMapInfo* MetaspaceShared::open_dynamic_archive() {
if (DynamicDumpSharedSpaces) {
return NULL;
}
if (Arguments::GetSharedDynamicArchivePath() == NULL) {
const char* dynamic_archive = Arguments::GetSharedDynamicArchivePath();
if (dynamic_archive == nullptr) {
return NULL;
}
FileMapInfo* mapinfo = new FileMapInfo(false);
FileMapInfo* mapinfo = new FileMapInfo(dynamic_archive, false);
if (!mapinfo->initialize()) {
delete(mapinfo);
return NULL;

View File

@ -3140,6 +3140,17 @@ jint Arguments::finalize_vm_init_args(bool patch_mod_javabase) {
DynamicDumpSharedSpaces = true;
}
if (AutoCreateSharedArchive) {
if (SharedArchiveFile == NULL) {
log_warning(cds)("-XX:+AutoCreateSharedArchive requires -XX:SharedArchiveFile");
return JNI_ERR;
}
if (ArchiveClassesAtExit != NULL) {
log_warning(cds)("-XX:+AutoCreateSharedArchive does not work with ArchiveClassesAtExit");
return JNI_ERR;
}
}
if (UseSharedSpaces && patch_mod_javabase) {
no_shared_spaces("CDS is disabled when " JAVA_BASE_NAME " module is patched.");
}
@ -3487,9 +3498,6 @@ void Arguments::extract_shared_archive_paths(const char* archive_path,
char* cur_path = NEW_C_HEAP_ARRAY(char, len + 1, mtInternal);
strncpy(cur_path, begin_ptr, len);
cur_path[len] = '\0';
if (!FileMapInfo::check_archive((const char*)cur_path, true /*is_static*/)) {
return;
}
*base_archive_path = cur_path;
begin_ptr = ++end_ptr;
@ -3501,9 +3509,6 @@ void Arguments::extract_shared_archive_paths(const char* archive_path,
len = end_ptr - begin_ptr;
cur_path = NEW_C_HEAP_ARRAY(char, len + 1, mtInternal);
strncpy(cur_path, begin_ptr, len + 1);
if (!FileMapInfo::check_archive((const char*)cur_path, false /*is_static*/)) {
return;
}
*top_archive_path = cur_path;
}
@ -3556,7 +3561,16 @@ void Arguments::init_shared_archive_paths() {
bool success =
FileMapInfo::get_base_archive_name_from_header(SharedArchiveFile, &base_archive_path);
if (!success) {
// If +AutoCreateSharedArchive and the specified shared archive does not exist,
// regenerate the dynamic archive base on default archive.
if (AutoCreateSharedArchive && !os::file_exists(SharedArchiveFile)) {
DynamicDumpSharedSpaces = true;
ArchiveClassesAtExit = const_cast<char *>(SharedArchiveFile);
SharedArchivePath = get_default_shared_archive_path();
SharedArchiveFile = nullptr;
} else {
no_shared_spaces("invalid archive");
}
} else if (base_archive_path == NULL) {
// User has specified a single archive, which is a static archive.
SharedArchivePath = const_cast<char *>(SharedArchiveFile);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, 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
@ -1802,6 +1802,9 @@ const intx ObjectAlignmentInBytes = 8;
product(bool, RecordDynamicDumpInfo, false, \
"Record class info for jcmd VM.cds dynamic_dump") \
\
product(bool, AutoCreateSharedArchive, false, \
"Create shared archive at exit if cds mapping failed") \
\
product(bool, PrintSharedArchiveAndExit, false, \
"Print shared archive file contents") \
\

View File

@ -47,17 +47,19 @@ public class MoveJDKTest {
public static void main(String[] args) throws Exception {
String java_home_src = System.getProperty("java.home");
String java_home_dst = CDSTestUtils.getOutputDir() + File.separator + "moved_jdk";
String homeJava = java_home_src + File.separator + "bin" + File.separator + "java";
String dstJava = java_home_dst + File.separator + "bin" + File.separator + "java";
TestCommon.startNewArchiveName();
String jsaFile = TestCommon.getCurrentArchiveName();
String jsaOpt = "-XX:SharedArchiveFile=" + jsaFile;
{
ProcessBuilder pb = makeBuilder(java_home_src + "/bin/java", "-Xshare:dump", jsaOpt);
ProcessBuilder pb = CDSTestUtils.makeBuilder(homeJava, "-Xshare:dump", jsaOpt);
TestCommon.executeAndLog(pb, "dump")
.shouldHaveExitValue(0);
}
{
ProcessBuilder pb = makeBuilder(java_home_src + "/bin/java",
ProcessBuilder pb = CDSTestUtils.makeBuilder(homeJava,
"-Xshare:auto",
jsaOpt,
"-Xlog:class+path=info",
@ -68,12 +70,12 @@ public class MoveJDKTest {
out.shouldNotContain("BOOT classpath mismatch");
}
clone(new File(java_home_src), new File(java_home_dst));
CDSTestUtils.clone(new File(java_home_src), new File(java_home_dst));
System.out.println("============== Cloned JDK at " + java_home_dst);
// Test runtime with cloned JDK
{
ProcessBuilder pb = makeBuilder(java_home_dst + "/bin/java",
ProcessBuilder pb = CDSTestUtils.makeBuilder(dstJava,
"-Xshare:auto",
jsaOpt,
"-Xlog:class+path=info",
@ -89,7 +91,7 @@ public class MoveJDKTest {
String fake_modules = copyFakeModulesFromHelloJar();
String dumptimeBootAppendOpt = "-Xbootclasspath/a:" + fake_modules;
{
ProcessBuilder pb = makeBuilder(java_home_src + "/bin/java",
ProcessBuilder pb = CDSTestUtils.makeBuilder(homeJava,
"-Xshare:dump",
dumptimeBootAppendOpt,
jsaOpt);
@ -98,7 +100,7 @@ public class MoveJDKTest {
}
{
String runtimeBootAppendOpt = dumptimeBootAppendOpt + System.getProperty("path.separator") + helloJar;
ProcessBuilder pb = makeBuilder(java_home_dst + "/bin/java",
ProcessBuilder pb = CDSTestUtils.makeBuilder(dstJava,
"-Xshare:auto",
runtimeBootAppendOpt,
jsaOpt,
@ -111,78 +113,17 @@ public class MoveJDKTest {
}
// Test with no modules image in the <java home>/lib directory
renameModulesFile(java_home_dst);
String locDir = java_home_dst + File.separator + "lib";
CDSTestUtils.rename(new File(locDir + File.separator + "modules"),
new File(locDir + File.separator + "orig-modules"));
{
ProcessBuilder pb = makeBuilder(java_home_dst + "/bin/java",
"-version");
ProcessBuilder pb = CDSTestUtils.makeBuilder(dstJava, "-version");
OutputAnalyzer out = TestCommon.executeAndLog(pb, "exec-missing-modules");
out.shouldHaveExitValue(1);
out.shouldContain("Failed setting boot class path.");
}
}
// Do a cheap clone of the JDK. Most files can be sym-linked. However, $JAVA_HOME/bin/java and $JAVA_HOME/lib/.../libjvm.so"
// must be copied, because the java.home property is derived from the canonicalized paths of these 2 files.
static void clone(File src, File dst) throws Exception {
if (dst.exists()) {
if (!dst.isDirectory()) {
throw new RuntimeException("Not a directory :" + dst);
}
} else {
if (!dst.mkdir()) {
throw new RuntimeException("Cannot create directory: " + dst);
}
}
final String jvmLib = System.mapLibraryName("jvm");
for (String child : src.list()) {
if (child.equals(".") || child.equals("..")) {
continue;
}
File child_src = new File(src, child);
File child_dst = new File(dst, child);
if (child_dst.exists()) {
throw new RuntimeException("Already exists: " + child_dst);
}
if (child_src.isFile()) {
if (child.equals(jvmLib) || child.equals("java")) {
Files.copy(child_src.toPath(), /* copy data to -> */ child_dst.toPath());
} else {
Files.createSymbolicLink(child_dst.toPath(), /* link to -> */ child_src.toPath());
}
} else {
clone(child_src, child_dst);
}
}
}
static void renameModulesFile(String javaHome) throws Exception {
String modulesDir = javaHome + File.separator + "lib";
File origModules = new File(modulesDir, "modules");
if (!origModules.exists()) {
throw new RuntimeException("modules file not found");
}
File renamedModules = new File(modulesDir, "orig_modules");
if (renamedModules.exists()) {
throw new RuntimeException("found orig_modules unexpectedly");
}
boolean success = origModules.renameTo(renamedModules);
if (!success) {
throw new RuntimeException("rename modules file failed");
}
}
static ProcessBuilder makeBuilder(String... args) throws Exception {
System.out.print("[");
for (String s : args) {
System.out.print(" " + s);
}
System.out.println(" ]");
return new ProcessBuilder(args);
}
private static String copyFakeModulesFromHelloJar() throws Exception {
String outDir = CDSTestUtils.getOutputDir();
String newFile = "hello.modules";

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2022, 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
@ -181,9 +181,7 @@ public class SharedArchiveConsistency {
copiedJsa = CDSArchiveUtils.copyArchiveFile(orgJsaFile, modVersion);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), version);
output = shareAuto ? TestCommon.execAuto(execArgs) : TestCommon.execCommon(execArgs);
output.shouldContain("The shared archive file has the wrong version")
.shouldContain("_version expected: " + currentCDSArchiveVersion)
.shouldContain("actual: " + version);
output.shouldContain("The shared archive file version " + version + " does not match the required version " + currentCDSArchiveVersion);
if (shareAuto) {
output.shouldContain(HELLO_WORLD);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, 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
@ -37,7 +37,6 @@ import sun.hotspot.WhiteBox;
class DynamicArchiveTestBase {
private static boolean executedIn_run = false;
private static boolean autoMode = false; // -Xshare:auto
private static final WhiteBox WB = WhiteBox.getWhiteBox();
public static interface DynamicArchiveTest {
@ -96,6 +95,19 @@ class DynamicArchiveTestBase {
return TestCommon.getNewArchiveName(stem);
}
/**
* Excute a JVM to dump a base archive by
* -Xshare:dump -XX:SharedArchiveFile=baseArchiveName
*/
public static Result dumpBaseArchive(String baseArchiveName, String... cmdLineSuffix)
throws Exception
{
OutputAnalyzer output = TestCommon.dumpBaseArchive(baseArchiveName, cmdLineSuffix);
CDSOptions opts = new CDSOptions();
opts.setXShareMode("dump");
return new Result(opts, output);
}
/**
* Execute a JVM using the base archive (given by baseArchiveName) with the command line
* (given by cmdLineSuffix). At JVM exit, dump all eligible classes into the top archive

View File

@ -0,0 +1,693 @@
/*
* Copyright (c) 2022, 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.
*
*/
/*
* @test
* @bug 8261455
* @summary test -XX:+AutoCreateSharedArchive feature
* @requires vm.cds
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/test-classes
* @build Hello
* @build sun.hotspot.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:./WhiteBox.jar TestAutoCreateSharedArchive verifySharedSpacesOff
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:./WhiteBox.jar TestAutoCreateSharedArchive verifySharedSpacesOn
*/
/*
* -XX:SharedArchiveFile can be specified in two styles:
*
* (A) Test with default base archive -XX:+SharedArchiveFile=<archive>
* (B) Test with the base archive specified: -XX:SharedArchiveFile=<base>:<top>
* all the following if not explained explicitly, run with flag -XX:+AutoCreateSharedArchive
*
* Note VerifySharedSpaces will affect output so the tests run twice: one with -XX:+VerifySharedSpaces and the other with -XX:-VerifySharedSpaces
*
* 10 Case (A)
*
* 10.01 run with non-existing archive should automatically create dynamic archive.
* If the JDK's default CDS archive cannot be loaded, print out warning, run continue without shared archive and no shared archive created at exit.
* 10.02 run with the created dynamic archive should pass.
* 10.03 run with the created dynamic archive and -XX:+AutoCreateSharedArchive should pass and no shared archive created at exit.
*
* 11 run with static archive.
* run with static archive should printout warning and continue, share or no share depends on the archive validation at exit,
* no shared archive (top) will be generated.
*
* 12 run with damaged magic should not regenerate dynamic archive.
* if magic is not expected, no shared archive will be regenerated at exit.
*
* 13 run with a bad versioned archive.
* 13.01 run with a bad versioned (< CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION) archive should not create dynamic archive at exit.
* 13.02 run with a bad versioned (> CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION) archive should create dynamic archive at exit.
*
* 14 run with an archive whose base name is not matched, no shared archive at exit.
*
* 15 run with an archive whose jvm_ident is corrupted should
* create dynamic archive at exit with -XX:-VerifySharedSpaces
* not create dynamic archive at exit with -XX:+VerifySharedSpaces
*
* 16 run with an archive only containing magic in the file (size of 4 bytes)
* the archive will be created at exit.
*
* 20 (case B)
*
* 20.01 dump base archive which will be used for dumping top archive.
* 20.02 dump top archive based on base archive obtained in 20.1.
* 20.03 run -XX:SharedArchiveFile=<base>:<top> to verify the archives.
* 20.04 run with -XX:SharedArchveFile=base:top (reversed)
*
* 21 Mismatched versions
* 21.01 if version of top archive is higher than CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION, the archive cannot be shared and will be
* regenerated at exit.
* 21.02 if version of top archive is lower than CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION, the archive cannot be shared and will be
* created at exit.
*
* 22 create an archive with dynamic magic number only
* archive will be created at exit if base can be shared.
*
* 23 mismatched jvm_indent in base/top archive
* 23.01 mismatched jvm_indent in top archive
* 23.02 mismatched jvm_indent in base archive
*
* 24 run with non-existing shared archives
* 24.01 run -Xshare:auto -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=base.jsa:non-exist-top.jsa
* The top archive will be regenerated.
* 24.02 run -Xshare:auto -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=non-exist-base.jsa:top.jsa
* top archive will not be shared if base archive failed to load.
*/
import java.io.IOException;
import java.io.File;
import java.nio.file.attribute.FileTime;
import java.nio.file.Files;
import java.nio.file.Paths;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.cds.CDSArchiveUtils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.helpers.ClassFileInstaller;
public class TestAutoCreateSharedArchive extends DynamicArchiveTestBase {
private static final String BASE_NAME = CDSTestUtils.getOutputFileName("base.jsa");
private static final String TOP_NAME = CDSTestUtils.getOutputFileName("top.jsa");
private static final String mainAppClass = "Hello";
private static final String HELLO_SOURCE = "Hello source: shared objects file (top)";
private static final String HELLO_WORLD = "Hello World";
private static boolean verifyOn = false;
private static int genericHeaderMinVersion = CDSArchiveUtils.getGenericHeaderMinVersion();
private static int currentCDSVersion = CDSArchiveUtils.getCurrentCDSArchiveVersion();
public static void main(String[] args) throws Exception {
if (args.length != 1 || (!args[0].equals("verifySharedSpacesOff") && !args[0].equals("verifySharedSpacesOn"))) {
throw new RuntimeException("Must run with verifySharedSpacesOff or verifySharedSpacesOn");
}
verifyOn = args[0].equals("verifySharedSpacesOn");
runTest(TestAutoCreateSharedArchive::testAutoCreateSharedArchive);
}
public static void checkFileExists(String fileName) throws Exception {
File file = new File(fileName);
if (!file.exists()) {
throw new IOException("Archive " + fileName + " is not automatically created");
}
}
public static String startNewArchive(String testName) {
String newArchiveName = TestCommon.getNewArchiveName(testName);
TestCommon.setCurrentArchiveName(newArchiveName);
return newArchiveName;
}
public static void print(String message) {
System.out.println(message);
}
private static void testAutoCreateSharedArchive() throws Exception {
String appJar = ClassFileInstaller.getJarPath("hello.jar");
boolean fileModified = false;
String verifySharedSpaces = verifyOn ? "-XX:+VerifySharedSpaces" : "-XX:-VerifySharedSpaces";
File archiveFile = new File(TOP_NAME);
if (archiveFile.exists()) {
archiveFile.delete();
}
// dump a static archive, used later.
// 0. Dump a static archive
print("0. dump a static archive " + BASE_NAME);
dumpBaseArchive(BASE_NAME,
"-Xlog:cds",
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0);
});
checkFileExists(BASE_NAME);
// The list numbers try to match JDK-8272331 (CSR for JDK-8261455) test items but not exactly matched.
// 10 non-existing archive should automatically create dynamic archive based on default shared archive
// if base archive loaded.
print("10 Test with default base shared archive");
print(" 10.01 run with non-existing archive should automatically create dynamic archive");
File fileTop = new File(TOP_NAME);
if (fileTop.exists()) {
fileTop.delete();
}
run(TOP_NAME,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain("Dumping shared data to file:")
.shouldContain(TOP_NAME);
});
checkFileExists(TOP_NAME);
//10.02 run with the created dynamic archive should pass
print(" 10.02 run with the created dynamic archive should pass");
run(TOP_NAME,
"-Xlog:cds",
"-Xlog:class+load",
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain(HELLO_SOURCE);
});
// remember the FileTime
FileTime ft1 = Files.getLastModifiedTime(Paths.get(TOP_NAME));
// 10.03 run with the created dynamic archive with -XX:+AutoCreateSharedArchive should pass
// archive should not be created again.
print(" 10.03 run with the created dynamic archive with -XX:+AutoCreateSharedArchive should pass");
run(TOP_NAME,
"-Xlog:cds",
"-Xlog:class+load",
"-Xlog:cds+dynamic=info",
"-XX:+AutoCreateSharedArchive",
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain(HELLO_SOURCE)
.shouldNotContain("Dumping shared data to file");
});
FileTime ft2 = Files.getLastModifiedTime(Paths.get(TOP_NAME));
fileModified = !ft2.equals(ft1);
if (fileModified) {
throw new RuntimeException("Archive file " + TOP_NAME + " should not be updated");
}
// 11 run with static archive
print("11 run with static archive");
ft1 = Files.getLastModifiedTime(Paths.get(BASE_NAME));
run(BASE_NAME,
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain("AutoCreateSharedArchive is ignored because " + BASE_NAME + " is a static archive")
.shouldNotContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(BASE_NAME));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Run -XX:+AutoCreateSharedArchive on static archive create new archive");
}
// 12 run with damaged magic should not regenerate dynamic archive
print("12 run with damaged magic should not regenerate dynamic archive");
String modMagic = startNewArchive("modify-magic");
File copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modMagic);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetMagic(), 0x1234);
ft1 = Files.getLastModifiedTime(Paths.get(modMagic));
run(modMagic,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldNotContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(modMagic));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Shared archive " + modMagic + " should not automatically be generated");
}
// 13 run with a bad versioned (< genericHeaderMinVersion) archive
print("13 run with a bad versioned archive");
print(" 13.01 run with a bad versioned (< genericHeaderMinVersion) archive should not create new archive");
String modVersion = startNewArchive("modify-version-b");
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modVersion);
final int version1 = genericHeaderMinVersion - 1;
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), version1);
ft1 = Files.getLastModifiedTime(Paths.get(modVersion));
run(modVersion,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain("Cannot handle shared archive file version " + version1 + ". Must be at least " + genericHeaderMinVersion)
.shouldContain("Unable to use shared archive: invalid archive")
.shouldNotContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(modVersion));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Run -XX:+AutoCreateSharedArchive with lower version archive " + modVersion + " should not create new archive");
}
// 13.02 run with a bad versioned (> currentCDSVersion) archive
print(" 13.02 run with a bad versioned (> currentCDSVersion) archive");
modVersion = startNewArchive("modify-version-d");
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modVersion);
final int version2 = currentCDSVersion + 1;
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), version2);
ft1 = Files.getLastModifiedTime(Paths.get(modVersion));
run(modVersion,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain("The shared archive file version " + version2 + " does not match the required version " + currentCDSVersion)
.shouldContain("UseSharedSpaces: The shared archive file has the wrong version")
.shouldContain("UseSharedSpaces: Initialize dynamic archive failed")
.shouldContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(modVersion));
fileModified = !ft1.equals(ft2);
if (!fileModified) {
throw new RuntimeException("Run -XX:+AutoCreateSharedArchive with higher version archive " + modVersion + " should create new archive");
}
// 14 run with an archive whose base name is not matched, no share
print("14 run with an archive whose base name is not matched, no share");
String baseNameMismatch= startNewArchive("basename-mismatch");
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, baseNameMismatch);
int nameSize = CDSArchiveUtils.baseArchiveNameSize(copiedJsa);
int offset = CDSArchiveUtils.baseArchiveNameOffset(copiedJsa);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameSize - 4; i++) {
sb.append('Z');
}
sb.append(".jsa");
sb.append('\0');
String newName = sb.toString();
CDSArchiveUtils.writeData(copiedJsa, offset, newName.getBytes());
ft1 = Files.getLastModifiedTime(Paths.get(baseNameMismatch));
run(baseNameMismatch,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldNotContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(baseNameMismatch));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Shared archive " + baseNameMismatch+ " should not automatically be generated");
}
// 15 mismatched jvm_indent in archive, create (-VerifySharedSpaces) or not (-XX:+VerifySharedSpaces) create the new archive
print("15 mismatched jvm_indent in archive, " + (verifyOn ? "-XX:+VerifySharedSpaces not " : "-XX:-VerifySharedSpaces ") + "create new archive");
String modJvmIdent = startNewArchive("modify-jvmident");
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modJvmIdent);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetJvmIdent(), 0x65656565);
ft1 = Files.getLastModifiedTime(Paths.get(modJvmIdent));
run(modJvmIdent,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0);
if (verifyOn) {
output.shouldContain("UseSharedSpaces: Header checksum verification failed")
.shouldContain("Unable to use shared archive: invalid archive")
.shouldNotContain("Dumping shared data to file");
} else {
output.shouldContain(HELLO_WORLD)
.shouldContain("Dumping shared data to file");
}
});
ft2 = Files.getLastModifiedTime(Paths.get(modJvmIdent));
fileModified = !ft1.equals(ft2);
if (verifyOn) {
if (fileModified) {
throw new RuntimeException("Shared archive " + modJvmIdent + " should not be generated");
}
} else {
if (!fileModified) {
throw new RuntimeException("Shared archive " + modJvmIdent + " should be generated");
}
}
// 16 run with an archive of only containing dynamic magic (size of 4) will not create new archive at exit
print("16 run with an archive of only containing dynamic magic (size of 4) will not create new archive at exit");
String magicOnly = startNewArchive("magic-only");
copiedJsa = CDSArchiveUtils.createMagicOnlyFile(magicOnly, false/*dynamic*/);
ft1 = Files.getLastModifiedTime(Paths.get(magicOnly));
run(magicOnly,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain("Unable to read generic CDS file map header from shared archive")
.shouldNotContain("Dumping shared data to file:");
});
ft2 = Files.getLastModifiedTime(Paths.get(magicOnly));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Shared archive " + magicOnly + " should not automatically be generated");
}
// Do some base tests for -XX:SharedArchiveFile=base:top, they should be same as default archive as base.
// delete top archive
if (archiveFile.exists()) {
archiveFile.delete();
}
// delete base archive
File baseFile = new File(BASE_NAME);
if (baseFile.exists()) {
baseFile.delete();
}
// 20 Testing with -XX:SharedArchiveFile=base:top
print("20 Testing with -XX:SharedArchiveFile=base:top");
// 20.01 dump base archive and top archive
print(" 20.01 dump base archive " + BASE_NAME);
dumpBaseArchive(BASE_NAME, "-Xlog:cds")
.assertNormalExit(output -> {
output.shouldHaveExitValue(0);
});
checkFileExists(BASE_NAME);
// 20.02 dump top based on base
print(" 20.02 dump top based on base");
dump2(BASE_NAME, TOP_NAME,
"-Xlog:cds",
"-cp", appJar, mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain("Dumping shared data to file:")
.shouldContain(TOP_NAME);
});
checkFileExists(TOP_NAME);
// 20.03 run with -XX:SharedArchveFile=base:top
print(" 20.03 run with -XX:SharedArchveFile=base:top");
run2(BASE_NAME, TOP_NAME,
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
"-Xlog:class+load",
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_SOURCE);
});
// 20.04 run with -XX:SharedArchveFile=top:base (reversed)
print(" 20.04 run with -XX:SharedArchveFile=top:base (reversed)");
run2(TOP_NAME, BASE_NAME,
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
"-Xlog:class+load",
"-cp", appJar,
mainAppClass)
.assertAbnormalExit(output -> {
output.shouldHaveExitValue(1)
.shouldContain("Not a base shared archive: " + TOP_NAME)
.shouldContain("An error has occurred while processing the shared archive file")
.shouldNotContain(HELLO_WORLD);
});
// 21 Mismatched versions
print("21 Mismatched versions");
// 21.01 top version is lower than CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION, regenerate top
print(" 21.01 top version is lower than CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION, regenerate top");
String versionB = startNewArchive("modify-version-B");
archiveFile = new File(TOP_NAME);
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, versionB);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), version1);
ft1 = Files.getLastModifiedTime(Paths.get(versionB));
run2(BASE_NAME, versionB,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD)
.shouldContain("Cannot handle shared archive file version " + version1)
.shouldContain(versionB)
.shouldContain("Dumping shared data to file:");
});
ft2 = Files.getLastModifiedTime(Paths.get(versionB));
fileModified = !ft1.equals(ft2);
if (!fileModified) {
throw new RuntimeException("Shared archive " + versionB + " should automatically be generated");
}
// 21.02 top version is higher than CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION, no share for top, create archive at exit
print(" 21.02 top version is higher than CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION, no share for top, create archive at exit");
String versionF = startNewArchive("versionF");
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, versionF);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), version2);
ft1 = Files.getLastModifiedTime(Paths.get(versionF));
run2(BASE_NAME, versionF,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldContain("The shared archive file version " + version2 + " does not match the required version " + currentCDSVersion)
.shouldContain(HELLO_WORLD)
.shouldContain("Dumping shared data to file:");
});
ft2 = Files.getLastModifiedTime(Paths.get(versionB));
fileModified = !ft1.equals(ft2);
if (!fileModified) {
throw new RuntimeException("Shared archive " + versionB + " should be created at exit");
}
// 22 create an archive with dynamic magic number only
// archive will be created at exit if base can be shared.
print("22 create an archive with dynamic magic number only");
copiedJsa = CDSArchiveUtils.createMagicOnlyFile(magicOnly, false /*dynamic*/);
ft1 = Files.getLastModifiedTime(Paths.get(magicOnly));
run2(BASE_NAME, magicOnly,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldContain(HELLO_WORLD)
.shouldContain("Unable to read generic CDS file map header from shared archive")
.shouldContain("Dumping shared data to file:");
});
ft2 = Files.getLastModifiedTime(Paths.get(magicOnly));
fileModified = !ft1.equals(ft2);
if (!fileModified) {
throw new RuntimeException("Shared archive " + magicOnly + " should be created at exit");
}
// 23 mismatched jvm_indent in top or base archive
// 23.01 mismatched jvm_indent in top archive
print(" 23.01 mismatched jvm_indent in top archive");
String modJvmIdentTop = startNewArchive("modify-jvmident-top");
copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modJvmIdentTop);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetJvmIdent(), 0x65656565);
ft1 = Files.getLastModifiedTime(Paths.get(modJvmIdentTop));
run2(BASE_NAME, modJvmIdentTop,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0);
if (verifyOn) {
output.shouldContain("UseSharedSpaces: Header checksum verification failed");
}
output.shouldContain(HELLO_WORLD)
.shouldContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(modJvmIdentTop));
fileModified = !ft1.equals(ft2);
if (!fileModified) {
throw new RuntimeException("Shared archive " + modJvmIdentTop + " should be generated");
}
// 23.02 mismatched jvm_indent in base archive
print(" 23.02 mismatched jvm_indent in base archive");
String modJvmIdentBase = startNewArchive("modify-jvmident-base");
copiedJsa = CDSArchiveUtils.copyArchiveFile(new File(BASE_NAME), modJvmIdentBase);
CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetJvmIdent(), 0x65656565);
ft1 = Files.getLastModifiedTime(Paths.get(TOP_NAME));
run2(modJvmIdentBase, TOP_NAME,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldHaveExitValue(0)
.shouldContain(HELLO_WORLD);
if (verifyOn) {
output.shouldContain("UseSharedSpaces: Header checksum verification failed");
}
output.shouldContain("Unable to map shared spaces")
.shouldNotContain("Dumping shared data to file");
});
ft2 = Files.getLastModifiedTime(Paths.get(TOP_NAME));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Shared archive " + TOP_NAME + " should not be generated");
}
// 24 run with non-existing shared archives
print("24 run with non-existing shared archives");
// 24.01 run -Xshare:auto -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=base.jsa:non-exist-top.jsa
print(" 24.01 run -Xshare:auto -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=base.jsa:non-exist-top.jsa");
String nonExistTop = "non-existing-top.jsa";
File fileNonExist = new File(nonExistTop);
if (fileNonExist.exists()) {
fileNonExist.delete();
}
run2(BASE_NAME, nonExistTop,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldContain("Specified shared archive not found (" + nonExistTop + ")")
.shouldContain(HELLO_WORLD)
.shouldContain("Dumping shared data to file:");
});
if (!fileNonExist.exists()) {
throw new RuntimeException("Shared archive " + nonExistTop + " should be created at exit");
}
// 24.02 run -Xshare:auto -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=non-exist-base.jsa:top.jsa
print(" 24.02 run -Xshare:auto -XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=non-exist-base.jsa:top.jsa");
String nonExistBase = "non-existing-base.jsa";
fileNonExist = new File(nonExistBase);
if (fileNonExist.exists()) {
fileNonExist.delete();
}
ft1 = Files.getLastModifiedTime(Paths.get(TOP_NAME));
run2(nonExistBase, TOP_NAME,
"-Xshare:auto",
"-XX:+AutoCreateSharedArchive",
"-Xlog:cds",
"-Xlog:cds+dynamic=info",
verifySharedSpaces,
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldContain("Specified shared archive not found (" + nonExistBase + ")")
.shouldContain(HELLO_WORLD)
.shouldNotContain("Dumping shared data to file:");
});
ft2 = Files.getLastModifiedTime(Paths.get(TOP_NAME));
fileModified = !ft1.equals(ft2);
if (fileModified) {
throw new RuntimeException("Shared archive " + TOP_NAME + " should not be created at exit");
}
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2022, 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.
*
*/
/*
* @test
* @summary Test -XX:+AutoCreateSharedArchive on a copied JDK without default shared archive
* @bug 8261455
* @requires vm.cds
* @requires vm.flagless
* @comment This test doesn't work on Windows because it depends on symlinks
* @requires os.family != "windows"
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @compile ../test-classes/Hello.java
* @run driver TestAutoCreateSharedArchiveNoDefaultArchive
*/
import java.io.File;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.process.OutputAnalyzer;
public class TestAutoCreateSharedArchiveNoDefaultArchive {
public static void main(String[] args) throws Exception {
String mainClass = "Hello";
String java_home_src = System.getProperty("java.home");
String java_home_dst = CDSTestUtils.getOutputDir() + File.separator + "moved_jdk";
CDSTestUtils.clone(new File(java_home_src), new File(java_home_dst));
System.out.println("======== Cloned JDK at " + java_home_dst);
String homeJava = java_home_src + File.separator + "bin" + File.separator + "java";
String dstJava = java_home_dst + File.separator + "bin" + File.separator + "java";
TestCommon.startNewArchiveName();
String jsaFileName = TestCommon.getCurrentArchiveName();
File jsaFile = new File(jsaFileName);
if (jsaFile.exists()) {
jsaFile.delete();
}
String jsaOpt = "-XX:SharedArchiveFile=" + jsaFileName;
String autoCreateArchive = "-XX:+AutoCreateSharedArchive";
{
ProcessBuilder pb = CDSTestUtils.makeBuilder(homeJava,
"-Xshare:dump",
jsaOpt);
TestCommon.executeAndLog(pb, "dump")
.shouldHaveExitValue(0);
}
{
ProcessBuilder pb = CDSTestUtils.makeBuilder(homeJava,
"-Xshare:auto",
jsaOpt,
"-Xlog:class+path=info",
"-version");
OutputAnalyzer out = TestCommon.executeAndLog(pb, "exec-src");
out.shouldHaveExitValue(0);
out.shouldNotContain("shared class paths mismatch");
out.shouldNotContain("BOOT classpath mismatch");
}
String helloJar = JarBuilder.getOrCreateHelloJar();
if (jsaFile.exists()) {
jsaFile.delete();
}
// Test runtime with cloned JDK
System.out.println("======== run with cloned jdk to created dynamic shared archive at exit");
{
ProcessBuilder pb = CDSTestUtils.makeBuilder(dstJava,
"-Xshare:auto",
autoCreateArchive,
jsaOpt,
"-Xlog:cds",
"-Xlog:class+path=info",
"-cp", helloJar,
mainClass);
OutputAnalyzer out = TestCommon.executeAndLog(pb, "exec-dst");
out.shouldHaveExitValue(0);
out.shouldContain("Dumping shared data to file");
if (!jsaFile.exists()) {
throw new RuntimeException("Shared archive " + jsaFileName + " should be created at exit");
}
}
// Now rename classes.jsa to old-classes.jsa
String dstDir = java_home_dst + File.separator + "lib" + File.separator + "server";
CDSTestUtils.rename(new File(dstDir + File.separator + "classes.jsa"),
new File(dstDir + File.separator + "old-classes.jsa"));
System.out.println("======= renamed classes.jsa to old-classes.jsa");
{
ProcessBuilder pb = CDSTestUtils.makeBuilder(dstJava,
"-Xlog:cds",
"-version");
TestCommon.executeAndLog(pb, "show-version")
.shouldHaveExitValue(0)
.shouldContain("UseSharedSpaces: Initialize static archive failed")
.shouldContain("UseSharedSpaces: Unable to map shared spaces")
.shouldContain("mixed mode")
.shouldNotContain("sharing");
}
// delete existing jsa file
if (jsaFile.exists()) {
jsaFile.delete();
}
System.out.println("======= run with no default shared archive should not create shared archive at exit");
{
ProcessBuilder pb = CDSTestUtils.makeBuilder(dstJava,
"-Xshare:auto",
autoCreateArchive,
jsaOpt,
"-Xlog:cds",
"-Xlog:class+path=info",
"-cp", helloJar,
mainClass);
TestCommon.executeAndLog(pb, "no-default-archive")
.shouldHaveExitValue(0)
.shouldContain("UseSharedSpaces: Initialize static archive failed")
.shouldContain("UseSharedSpaces: Unable to map shared spaces")
.shouldNotContain("Dumping shared data to file");
if (jsaFile.exists()) {
throw new RuntimeException("Archive file " + jsaFileName + " should not be created at exit");
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2022, 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
@ -341,6 +341,18 @@ public class CDSArchiveUtils {
return newJsaFile;
}
public static File createMagicOnlyFile(String fileName, boolean createStatic) throws Exception {
File file = new File(fileName);
if (file.exists()) {
file.delete();
}
try (FileOutputStream out = new FileOutputStream(file)) {
ByteBuffer buffer = ByteBuffer.allocate(4).putInt(createStatic ? staticMagic: dynamicMagic);
out.write(buffer.array(), 0, 4);
}
return file;
}
private static FileChannel getFileChannel(File file, boolean write) throws Exception {
List<StandardOpenOption> arry = new ArrayList<StandardOpenOption>();
arry.add(READ);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2022, 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
@ -26,6 +26,9 @@ import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.CopyOption;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@ -684,4 +687,78 @@ public class CDSTestUtils {
private static boolean isAsciiPrintable(char ch) {
return ch >= 32 && ch < 127;
}
// JDK utility
// Do a cheap clone of the JDK. Most files can be sym-linked. However, $JAVA_HOME/bin/java and $JAVA_HOME/lib/.../libjvm.so"
// must be copied, because the java.home property is derived from the canonicalized paths of these 2 files.
// Set a list of {jvm, "java"} which will be physically copied. If a file needs copied physically, add it to the list.
private static String[] phCopied = {System.mapLibraryName("jvm"), "java"};
public static void clone(File src, File dst) throws Exception {
if (dst.exists()) {
if (!dst.isDirectory()) {
throw new RuntimeException("Not a directory :" + dst);
}
} else {
if (!dst.mkdir()) {
throw new RuntimeException("Cannot create directory: " + dst);
}
}
// final String jvmLib = System.mapLibraryName("jvm");
for (String child : src.list()) {
if (child.equals(".") || child.equals("..")) {
continue;
}
File child_src = new File(src, child);
File child_dst = new File(dst, child);
if (child_dst.exists()) {
throw new RuntimeException("Already exists: " + child_dst);
}
if (child_src.isFile()) {
boolean needPhCopy = false;
for (String target : phCopied) {
if (child.equals(target)) {
needPhCopy = true;
break;
}
}
if (needPhCopy) {
Files.copy(child_src.toPath(), /* copy data to -> */ child_dst.toPath(),
new CopyOption[] { StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES});
} else {
Files.createSymbolicLink(child_dst.toPath(), /* link to -> */ child_src.toPath());
}
} else {
clone(child_src, child_dst);
}
}
}
// modulesDir, like $JDK/lib
// oldName, module name under modulesDir
// newName, new name for oldName
public static void rename(File fromFile, File toFile) throws Exception {
if (!fromFile.exists()) {
throw new RuntimeException(fromFile.getName() + " does not exist");
}
if (toFile.exists()) {
throw new RuntimeException(toFile.getName() + " already exists");
}
boolean success = fromFile.renameTo(toFile);
if (!success) {
throw new RuntimeException("rename file " + fromFile.getName()+ " to " + toFile.getName() + " failed");
}
}
public static ProcessBuilder makeBuilder(String... args) throws Exception {
System.out.print("[");
for (String s : args) {
System.out.print(" " + s);
}
System.out.println(" ]");
return new ProcessBuilder(args);
}
}