8259070: Add jcmd option to dump CDS

Reviewed-by: ccheung, iklam, mli
This commit is contained in:
Yumin Qi 2021-04-15 05:21:24 +00:00
parent 593194864a
commit e7cbeba866
23 changed files with 800 additions and 75 deletions

View file

@ -52,6 +52,8 @@ JVM_DefineClass
JVM_DefineClassWithSource
JVM_DesiredAssertionStatus
JVM_DumpAllStacks
JVM_DumpClassListToFile
JVM_DumpDynamicArchive
JVM_DumpThreads
JVM_FillInStackTrace
JVM_FindClassFromCaller

View file

@ -777,19 +777,6 @@ ClassPathZipEntry* ClassLoader::create_class_path_zip_entry(const char *path, bo
return NULL;
}
// returns true if entry already on class path
bool ClassLoader::contains_append_entry(const char* name) {
ClassPathEntry* e = first_append_entry();
while (e != NULL) {
// assume zip entries have been canonicalized
if (strcmp(name, e->name()) == 0) {
return true;
}
e = e->next();
}
return false;
}
// The boot append entries are added with a lock, and read lock free.
void ClassLoader::add_to_boot_append_entries(ClassPathEntry *new_entry) {
if (new_entry != NULL) {

View file

@ -385,9 +385,6 @@ class ClassLoader: AllStatic {
static jlong class_link_count();
static jlong class_link_time_ms();
// indicates if class path already contains a entry (exact match by name)
static bool contains_append_entry(const char* name);
// adds a class path to the boot append entries
static void add_to_boot_append_entries(ClassPathEntry* new_entry);

View file

@ -297,9 +297,11 @@
/* Type Annotations (JDK 8 and above) */ \
template(type_annotations_name, "typeAnnotations") \
/* used by CDS */ \
template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
template(generateLambdaFormHolderClasses_signature, "([Ljava/lang/String;)[Ljava/lang/Object;") \
template(dumpSharedArchive, "dumpSharedArchive") \
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)V") \
\
/* Intrinsic Annotation (JDK 9 and above) */ \
template(jdk_internal_vm_annotation_DontInline_signature, "Ljdk/internal/vm/annotation/DontInline;") \

View file

@ -200,6 +200,12 @@ JVM_GetRandomSeedForDumping();
JNIEXPORT void JNICALL
JVM_LogLambdaFormInvoker(JNIEnv* env, jstring line);
JNIEXPORT void JNICALL
JVM_DumpClassListToFile(JNIEnv* env, jstring fileName);
JNIEXPORT void JNICALL
JVM_DumpDynamicArchive(JNIEnv* env, jstring archiveName);
/*
* java.lang.Throwable
*/

View file

@ -27,6 +27,7 @@
#include "classfile/classLoaderData.inline.hpp"
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "classfile/vmSymbols.hpp"
#include "gc/shared/collectedHeap.hpp"
#include "gc/shared/gcVMOperations.hpp"
#include "gc/shared/gc_globals.hpp"
@ -330,6 +331,35 @@ public:
}
};
bool DynamicArchive::_has_been_dumped_once = false;
void DynamicArchive::dump(const char* archive_name, TRAPS) {
assert(UseSharedSpaces && RecordDynamicDumpInfo, "already checked in arguments.cpp?");
assert(ArchiveClassesAtExit == nullptr, "already checked in arguments.cpp?");
// During dynamic archive dumping, some of the data structures are overwritten so
// we cannot dump the dynamic archive again. TODO: this should be fixed.
if (has_been_dumped_once()) {
THROW_MSG(vmSymbols::java_lang_RuntimeException(),
"Dynamic dump has been done, and should only be done once");
} else {
// prevent multiple dumps.
set_has_been_dumped_once();
}
ArchiveClassesAtExit = archive_name;
if (Arguments::init_shared_archive_paths()) {
dump();
} else {
ArchiveClassesAtExit = nullptr;
THROW_MSG(vmSymbols::java_lang_RuntimeException(),
"Could not setup SharedDynamicArchivePath");
}
// prevent do dynamic dump at exit.
ArchiveClassesAtExit = nullptr;
if (!Arguments::init_shared_archive_paths()) {
THROW_MSG(vmSymbols::java_lang_RuntimeException(),
"Could not restore SharedDynamicArchivePath");
}
}
void DynamicArchive::dump() {
if (Arguments::GetSharedDynamicArchivePath() == NULL) {

View file

@ -58,8 +58,12 @@ public:
};
class DynamicArchive : AllStatic {
static bool _has_been_dumped_once;
public:
static void dump(const char* archive_name, TRAPS);
static void dump();
static bool has_been_dumped_once() { return _has_been_dumped_once; }
static void set_has_been_dumped_once() { _has_been_dumped_once = true; }
static bool is_mapped() { return FileMapInfo::dynamic_info() != NULL; }
static bool validate(FileMapInfo* dynamic_info);
};

View file

@ -145,6 +145,33 @@ static bool shared_base_valid(char* shared_base) {
#endif
}
class DumpClassListCLDClosure : public CLDClosure {
fileStream *_stream;
public:
DumpClassListCLDClosure(fileStream* f) : CLDClosure() { _stream = f; }
void do_cld(ClassLoaderData* cld) {
for (Klass* klass = cld->klasses(); klass != NULL; klass = klass->next_link()) {
if (klass->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(klass);
if (ik->is_shareable()) {
_stream->print_cr("%s", ik->name()->as_C_string());
}
}
}
}
};
void MetaspaceShared::dump_loaded_classes(const char* file_name, TRAPS) {
fileStream stream(file_name, "w");
if (stream.is_open()) {
MutexLocker lock(ClassLoaderDataGraph_lock);
DumpClassListCLDClosure collect_classes(&stream);
ClassLoaderDataGraph::loaded_cld_do(&collect_classes);
} else {
THROW_MSG(vmSymbols::java_io_IOException(), "Failed to open file");
}
}
static bool shared_base_too_high(char* specified_base, char* aligned_base, size_t cds_max) {
if (specified_base != NULL && aligned_base < specified_base) {
// SharedBaseAddress is very high (e.g., 0xffffffffffffff00) so

View file

@ -143,6 +143,8 @@ public:
// (Heap region alignments are decided by GC).
static size_t core_region_alignment();
static void rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread* thread, InstanceKlass* ik);
// print loaded classes names to file.
static void dump_loaded_classes(const char* file_name, TRAPS);
#endif
// Allocate a block of memory from the temporary "symbol" region.

View file

@ -3672,7 +3672,7 @@ const char* InstanceKlass::internal_name() const {
void InstanceKlass::print_class_load_logging(ClassLoaderData* loader_data,
const ModuleEntry* module_entry,
const ClassFileStream* cfs) const {
log_to_classlist(cfs);
log_to_classlist();
if (!log_is_enabled(Info, class, load)) {
return;
@ -4265,53 +4265,37 @@ unsigned char * InstanceKlass::get_cached_class_file_bytes() {
}
#endif
void InstanceKlass::log_to_classlist(const ClassFileStream* stream) const {
bool InstanceKlass::is_shareable() const {
#if INCLUDE_CDS
ClassLoaderData* loader_data = class_loader_data();
if (!SystemDictionaryShared::is_sharing_possible(loader_data)) {
return false;
}
if (is_hidden() || unsafe_anonymous_host() != NULL) {
return false;
}
if (module()->is_patched()) {
return false;
}
return true;
#else
return false;
#endif
}
void InstanceKlass::log_to_classlist() const {
#if INCLUDE_CDS
ResourceMark rm;
if (ClassListWriter::is_enabled()) {
if (!ClassLoader::has_jrt_entry()) {
warning("DumpLoadedClassList and CDS are not supported in exploded build");
DumpLoadedClassList = NULL;
return;
}
ClassLoaderData* loader_data = class_loader_data();
if (!SystemDictionaryShared::is_sharing_possible(loader_data)) {
return;
}
bool skip = false;
if (is_shared()) {
assert(stream == NULL, "shared class with stream");
if (is_hidden()) {
// Don't include archived lambda proxy class in the classlist.
assert(!is_non_strong_hidden(), "unexpected non-strong hidden class");
return;
}
} else {
assert(stream != NULL, "non-shared class without stream");
// skip hidden class and unsafe anonymous class.
if ( is_hidden() || unsafe_anonymous_host() != NULL) {
return;
}
oop class_loader = loader_data->class_loader();
if (class_loader == NULL || SystemDictionary::is_platform_class_loader(class_loader)) {
// For the boot and platform class loaders, skip classes that are not found in the
// java runtime image, such as those found in the --patch-module entries.
// These classes can't be loaded from the archive during runtime.
if (!stream->from_boot_loader_modules_image() && strncmp(stream->source(), "jrt:", 4) != 0) {
skip = true;
}
if (class_loader == NULL && ClassLoader::contains_append_entry(stream->source())) {
// .. but don't skip the boot classes that are loaded from -Xbootclasspath/a
// as they can be loaded from the archive during runtime.
skip = false;
}
}
}
ResourceMark rm;
if (skip) {
tty->print_cr("skip writing class %s from source %s to classlist file",
name()->as_C_string(), stream->source());
} else {
if (is_shareable()) {
ClassListWriter w;
w.stream()->print_cr("%s", name()->as_C_string());
w.stream()->flush();

View file

@ -361,6 +361,9 @@ class InstanceKlass: public Klass {
return (_misc_flags & shared_loader_type_bits()) == 0;
}
// Check if the class can be shared in CDS
bool is_shareable() const;
void clear_shared_class_loader_type() {
_misc_flags &= ~shared_loader_type_bits();
}
@ -1261,7 +1264,7 @@ private:
void mark_newly_obsolete_methods(Array<Method*>* old_methods, int emcp_method_count);
#endif
// log class name to classlist
void log_to_classlist(const ClassFileStream* cfs) const;
void log_to_classlist() const;
public:
// CDS support - remove and restore oops from metadata. Oops are not shared.
virtual void remove_unshareable_info();

View file

@ -3653,11 +3653,11 @@ JVM_ENTRY(jclass, JVM_LookupLambdaProxyClassFromArchive(JNIEnv* env,
JVM_END
JVM_ENTRY(jboolean, JVM_IsCDSDumpingEnabled(JNIEnv* env))
return Arguments::is_dumping_archive();
return Arguments::is_dumping_archive();
JVM_END
JVM_ENTRY(jboolean, JVM_IsSharingEnabled(JNIEnv* env))
return UseSharedSpaces;
return UseSharedSpaces;
JVM_END
JVM_ENTRY_NO_ENV(jlong, JVM_GetRandomSeedForDumping())
@ -3703,6 +3703,24 @@ JVM_ENTRY(void, JVM_LogLambdaFormInvoker(JNIEnv *env, jstring line))
#endif // INCLUDE_CDS
JVM_END
JVM_ENTRY(void, JVM_DumpClassListToFile(JNIEnv *env, jstring listFileName))
#if INCLUDE_CDS
ResourceMark rm(THREAD);
Handle file_handle(THREAD, JNIHandles::resolve_non_null(listFileName));
char* file_name = java_lang_String::as_utf8_string(file_handle());
MetaspaceShared::dump_loaded_classes(file_name, THREAD);
#endif // INCLUDE_CDS
JVM_END
JVM_ENTRY(void, JVM_DumpDynamicArchive(JNIEnv *env, jstring archiveName))
#if INCLUDE_CDS
ResourceMark rm(THREAD);
Handle file_handle(THREAD, JNIHandles::resolve_non_null(archiveName));
char* archive_name = java_lang_String::as_utf8_string(file_handle());
DynamicArchive::dump(archive_name, THREAD);
#endif // INCLUDE_CDS
JVM_END
// Returns an array of all live Thread objects (VM internal JavaThreads,
// jvmti agent threads, and JNI attaching threads are skipped)
// See CR 6404306 regarding JNI attaching threads

View file

@ -3114,9 +3114,19 @@ jint Arguments::finalize_vm_init_args(bool patch_mod_javabase) {
log_info(cds)("All non-system classes will be verified (-Xverify:remote) during CDS dump time.");
}
}
if (ArchiveClassesAtExit == NULL) {
FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, false);
// RecordDynamicDumpInfo is not compatible with ArchiveClassesAtExit
if (ArchiveClassesAtExit != NULL && RecordDynamicDumpInfo) {
log_info(cds)("RecordDynamicDumpInfo is for jcmd only, could not set with -XX:ArchiveClassesAtExit.");
return JNI_ERR;
}
if (ArchiveClassesAtExit == NULL && !RecordDynamicDumpInfo) {
FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, false);
} else {
FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, true);
}
if (UseSharedSpaces && patch_mod_javabase) {
no_shared_spaces("CDS is disabled when " JAVA_BASE_NAME " module is patched.");
}
@ -3497,6 +3507,11 @@ bool Arguments::init_shared_archive_paths() {
}
check_unsupported_dumping_properties();
SharedDynamicArchivePath = os::strdup_check_oom(ArchiveClassesAtExit, mtArguments);
} else {
if (SharedDynamicArchivePath != nullptr) {
os::free(SharedDynamicArchivePath);
SharedDynamicArchivePath = nullptr;
}
}
if (SharedArchiveFile == NULL) {
SharedArchivePath = get_default_shared_archive_path();

View file

@ -1889,6 +1889,9 @@ const intx ObjectAlignmentInBytes = 8;
product(bool, DynamicDumpSharedSpaces, false, \
"Dynamic archive") \
\
product(bool, RecordDynamicDumpInfo, false, \
"Record class info for jcmd VM.cds dynamic_dump") \
\
product(bool, PrintSharedArchiveAndExit, false, \
"Print shared archive file contents") \
\

View file

@ -141,6 +141,9 @@ void DCmdRegistrant::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<DebugOnCmdStartDCmd>(full_export, true, true));
#endif // INCLUDE_JVMTI
#if INCLUDE_CDS
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<DumpSharedArchiveDCmd>(full_export, true, false));
#endif // INCLUDE_CDS
}
#ifndef HAVE_EXTRA_DCMD
@ -924,6 +927,60 @@ void TouchedMethodsDCmd::execute(DCmdSource source, TRAPS) {
VM_DumpTouchedMethods dumper(output());
VMThread::execute(&dumper);
}
#if INCLUDE_CDS
DumpSharedArchiveDCmd::DumpSharedArchiveDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
_suboption("subcmd", "static_dump | dynamic_dump", "STRING", true),
_filename("filename", "Name of shared archive to be dumped", "STRING", false)
{
_dcmdparser.add_dcmd_argument(&_suboption);
_dcmdparser.add_dcmd_argument(&_filename);
}
void DumpSharedArchiveDCmd::execute(DCmdSource source, TRAPS) {
jboolean is_static;
const char* scmd = _suboption.value();
const char* file = _filename.value();
if (strcmp(scmd, "static_dump") == 0) {
is_static = JNI_TRUE;
output()->print_cr("Static dump:");
} else if (strcmp(scmd, "dynamic_dump") == 0) {
is_static = JNI_FALSE;
output()->print_cr("Dynamic dump:");
if (!UseSharedSpaces) {
output()->print_cr("Dynamic dump is unsupported when base CDS archive is not loaded");
return;
}
if (!RecordDynamicDumpInfo) {
output()->print_cr("Dump dynamic should run with -XX:+RecordDynamicDumpInfo");
return;
}
} else {
output()->print_cr("Invalid command for VM.cds, valid input is static_dump or dynamic_dump");
return;
}
// call CDS.dumpSharedArchive
Handle fileh;
if (file != NULL) {
fileh = java_lang_String::create_from_str(_filename.value(), CHECK);
}
Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS();
Klass* cds_klass = SystemDictionary::resolve_or_fail(cds_name, true /*throw error*/, CHECK);
JavaValue result(T_VOID);
JavaCallArguments args;
args.push_int(is_static);
args.push_oop(fileh);
JavaCalls::call_static(&result,
cds_klass,
vmSymbols::dumpSharedArchive(),
vmSymbols::dumpSharedArchive_signature(),
&args, CHECK);
}
#endif // INCLUDE_CDS
#if INCLUDE_JVMTI
extern "C" typedef char const* (JNICALL *debugInit_startDebuggingViaCommandPtr)(JNIEnv* env, jthread thread, char const** transport_name,
char const** address, jboolean* first_start);

View file

@ -399,6 +399,32 @@ public:
virtual void execute(DCmdSource source, TRAPS);
};
#if INCLUDE_CDS
class DumpSharedArchiveDCmd: public DCmdWithParser {
protected:
DCmdArgument<char*> _suboption; // option of VM.cds
DCmdArgument<char*> _filename; // file name, optional
public:
DumpSharedArchiveDCmd(outputStream* output, bool heap);
static const char* name() {
return "VM.cds";
}
static const char* description() {
return "Dump a static or dynamic shared archive including all shareable classes";
}
static const char* impact() {
return "Medium: Pause time depends on number of loaded classes";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission",
"monitor", NULL};
return p;
}
static int num_arguments();
virtual void execute(DCmdSource source, TRAPS);
};
#endif // INCLUDE_CDS
// See also: thread_dump in attachListener.cpp
class ThreadDumpDCmd : public DCmdWithParser {
protected:

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2021, 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
@ -25,7 +25,15 @@
package jdk.internal.misc;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
@ -63,6 +71,7 @@ public class CDS {
public static boolean isSharingEnabled() {
return isSharingEnabled;
}
private static native boolean isDumpingClassList0();
private static native boolean isDumpingArchive0();
private static native boolean isSharingEnabled0();
@ -195,4 +204,123 @@ public class CDS {
};
return retArray;
}
private static native void dumpClassList(String listFileName);
private static native void dumpDynamicArchive(String archiveFileName);
private static String drainOutput(InputStream stream, long pid, String tail, List<String> cmds) {
String fileName = "java_pid" + pid + "_" + tail;
new Thread( ()-> {
try (InputStreamReader isr = new InputStreamReader(stream);
BufferedReader rdr = new BufferedReader(isr);
PrintStream prt = new PrintStream(fileName)) {
prt.println("Command:");
for (String s : cmds) {
prt.print(s + " ");
}
prt.println("");
String line;
while((line = rdr.readLine()) != null) {
prt.println(line);
}
} catch (IOException e) {
throw new RuntimeException("IOExeption happens during drain stream to file " +
fileName + ": " + e.getMessage());
}}).start();
return fileName;
}
private static String[] excludeFlags = {
"-XX:DumpLoadedClassList=",
"-XX:+DumpSharedSpaces",
"-XX:+DynamicDumpSharedSpaces",
"-XX:+RecordDynamicDumpInfo",
"-Xshare:",
"-XX:SharedClassListFile=",
"-XX:SharedArchiveFile=",
"-XX:ArchiveClassesAtExit=",
"-XX:+UseSharedSpaces",
"-XX:+RequireSharedSpaces"};
private static boolean containsExcludedFlags(String testStr) {
for (String e : excludeFlags) {
if (testStr.contains(e)) {
return true;
}
}
return false;
}
/**
* called from jcmd VM.cds to dump static or dynamic shared archive
* @param isStatic true for dump static archive or false for dynnamic archive.
* @param fileName user input archive name, can be null.
*/
private static void dumpSharedArchive(boolean isStatic, String fileName) throws Exception {
String currentPid = String.valueOf(ProcessHandle.current().pid());
String archiveFile = fileName != null ? fileName :
"java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa");
// delete if archive file aready exists
File fileArchive = new File(archiveFile);
if (fileArchive.exists()) {
fileArchive.delete();
}
if (isStatic) {
String listFile = archiveFile + ".classlist";
File fileList = new File(listFile);
if (fileList.exists()) {
fileList.delete();
}
dumpClassList(listFile);
String jdkHome = System.getProperty("java.home");
String classPath = System.getProperty("java.class.path");
List<String> cmds = new ArrayList<String>();
cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java
cmds.add("-cp");
cmds.add(classPath);
cmds.add("-Xlog:cds");
cmds.add("-Xshare:dump");
cmds.add("-XX:SharedClassListFile=" + listFile);
cmds.add("-XX:SharedArchiveFile=" + archiveFile);
// All runtime args.
String[] vmArgs = VM.getRuntimeArguments();
if (vmArgs != null) {
for (String arg : vmArgs) {
if (arg != null && !containsExcludedFlags(arg)) {
cmds.add(arg);
}
}
}
Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0]));
// Drain stdout/stderr to files in new threads.
String stdOutFile = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds);
String stdErrFile = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds);
proc.waitFor();
// done, delete classlist file.
if (fileList.exists()) {
fileList.delete();
}
// Check if archive has been successfully dumped. We won't reach here if exception happens.
// Throw exception if file is not created.
if (!fileArchive.exists()) {
throw new RuntimeException("Archive file " + archiveFile +
" is not created, please check stdout file " +
stdOutFile + " or stderr file " +
stdErrFile + " for more detail");
}
} else {
dumpDynamicArchive(archiveFile);
if (!fileArchive.exists()) {
throw new RuntimeException("Archive file " + archiveFile +
" is not created, please check process " +
currentPid + " output for more detail");
}
}
// Everyting goes well, print out the file name.
System.out.println((isStatic ? "Static" : " Dynamic") + " dump to file " + archiveFile);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2021, 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
@ -63,3 +63,13 @@ JNIEXPORT void JNICALL
Java_jdk_internal_misc_CDS_logLambdaFormInvoker(JNIEnv *env, jclass jcls, jstring line) {
JVM_LogLambdaFormInvoker(env, line);
}
JNIEXPORT void JNICALL
Java_jdk_internal_misc_CDS_dumpClassList(JNIEnv *env, jclass jcls, jstring fileName) {
JVM_DumpClassListToFile(env, fileName);
}
JNIEXPORT void JNICALL
Java_jdk_internal_misc_CDS_dumpDynamicArchive(JNIEnv *env, jclass jcls, jstring archiveName) {
JVM_DumpDynamicArchive(env, archiveName);
}

View file

@ -324,6 +324,7 @@ hotspot_appcds_dynamic = \
-runtime/cds/appcds/javaldr/GCSharedStringsDuringDump.java \
-runtime/cds/appcds/javaldr/HumongousDuringDump.java \
-runtime/cds/appcds/javaldr/LockDuringDump.java \
-runtime/cds/appcds/jcmd/JCmdTest.java \
-runtime/cds/appcds/methodHandles \
-runtime/cds/appcds/sharedStrings \
-runtime/cds/appcds/ArchiveRelocationTest.java \

View file

@ -83,10 +83,6 @@ public class DumpClassList {
appClass[0])
.assertNormalExit(output -> {
output.shouldContain("hello world");
// skip classes outside of jrt image
output.shouldContain("skip writing class java/lang/NewClass");
// but classes on -Xbootclasspath/a should not be skipped
output.shouldNotContain("skip writing class boot/append/Foo");
});
CDSOptions opts = (new CDSOptions())

View file

@ -0,0 +1,362 @@
/*
* Copyright (c) 2021, 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 8259070
* @summary Test jcmd to dump static and dynamic shared archive.
* @requires vm.cds
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
* @modules jdk.jcmd/sun.tools.common:+open
* @compile ../test-classes/Hello.java
* @build sun.hotspot.WhiteBox
* @build JCmdTestLingeredApp JCmdTest
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm/timeout=480 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI JCmdTest
*/
import java.io.File;
import java.io.IOException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.dcmd.PidJcmdExecutor;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.Utils;
import jtreg.SkippedException;
import sun.hotspot.WhiteBox;
import java.io.InputStreamReader;
import java.io.BufferedReader;
public class JCmdTest {
static final String TEST_CLASSES[] = {"JCmdTestLingeredApp",
"jdk/test/lib/apps/LingeredApp",
"jdk/test/lib/apps/LingeredApp$1"};
static final String BOOT_CLASSES[] = {"Hello"};
static final String SUBCMD_STATIC_DUMP = "static_dump";
static final String SUBCMD_DYNAMIC_DUMP = "dynamic_dump";
static final String STATIC_DUMP_FILE = "mystatic";
static final String DYNAMIC_DUMP_FILE = "mydynamic";
static final String[] STATIC_MESSAGES = {"JCmdTestLingeredApp source: shared objects file",
"LingeredApp source: shared objects file",
"Hello source: shared objects file"};
static final String[] DYNAMIC_MESSAGES = {"JCmdTestLingeredApp source: shared objects file (top)",
"LingeredApp source: shared objects file (top)",
"Hello source: shared objects file (top)"};
static String testJar = null;
static String bootJar = null;
static String allJars = null;
private static void buildJar() throws Exception {
testJar = JarBuilder.build("test", TEST_CLASSES);
bootJar = JarBuilder.build("boot", BOOT_CLASSES);
System.out.println("Jar file created: " + testJar);
System.out.println("Jar file created: " + bootJar);
allJars = testJar+ File.pathSeparator + bootJar;
}
private static boolean argsContain(String[] args, String flag) {
for (String s: args) {
if (s.contains(flag)) {
return true;
}
}
return false;
}
private static boolean argsContainOpts(String[] args, String... opts) {
boolean allIn = true;
for (String f : opts) {
allIn &= argsContain(args, f);
if (!allIn) {
break;
}
}
return allIn;
}
private static LingeredApp createLingeredApp(String... args) throws Exception {
JCmdTestLingeredApp app = new JCmdTestLingeredApp();
try {
LingeredApp.startAppExactJvmOpts(app, args);
} catch (Exception e) {
// Check flags used.
if (argsContainOpts(args, new String[] {"-Xshare:off", "-XX:+RecordDynamicDumpInfo"}) ||
argsContainOpts(args, new String[] {"-XX:+RecordDynamicDumpInfo", "-XX:ArchiveClassesAtExit="})) {
// app exit premature due to incompactible args
return null;
}
Process proc = app.getProcess();
if (e instanceof IOException && proc.exitValue() == 0) {
// Process started and exit normally.
return null;
}
throw e;
}
return app;
}
static int logFileCount = 0;
private static void runWithArchiveFile(String archiveName, boolean useBoot, String... messages) throws Exception {
List<String> args = new ArrayList<String>();
if (useBoot) {
args.add("-Xbootclasspath/a:" + bootJar);
}
args.add("-cp");
if (useBoot) {
args.add(testJar);
} else {
args.add(allJars);
}
args.add("-Xshare:on");
args.add("-XX:SharedArchiveFile=" + archiveName);
args.add("-Xlog:class+load");
LingeredApp app = createLingeredApp(args.toArray(new String[0]));
app.setLogFileName("JCmdTest.log." + (logFileCount++));
app.stopApp();
String output = app.getOutput().getStdout();
if (messages != null) {
for (String msg : messages) {
if (!output.contains(msg)) {
throw new RuntimeException(msg + " missed from output");
}
}
}
}
private static void test(String jcmdSub, String archiveFile,
long pid, boolean useBoot, boolean expectOK, String... messages) throws Exception {
System.out.println("Expected: " + (expectOK ? "SUCCESS" : "FAIL"));
boolean isStatic = jcmdSub.equals(SUBCMD_STATIC_DUMP);
String fileName = archiveFile != null ? archiveFile :
("java_pid" + pid + (isStatic ? "_static" : "_dynamic") + ".jsa");
File file = new File(fileName);
if (file.exists()) {
file.delete();
}
String jcmd = "VM.cds " + jcmdSub;
if (archiveFile != null) {
jcmd += " " + archiveFile;
}
PidJcmdExecutor cmdExecutor = new PidJcmdExecutor(String.valueOf(pid));
OutputAnalyzer output = cmdExecutor.execute(jcmd, true/*silent*/);
if (expectOK) {
output.shouldHaveExitValue(0);
if (!file.exists()) {
throw new RuntimeException("Could not create shared archive: " + fileName);
} else {
runWithArchiveFile(fileName, useBoot, messages);
file.delete();
}
} else {
if (file.exists()) {
throw new RuntimeException("Should not create shared archive " + fileName);
}
}
}
private static void print2ln(String arg) {
System.out.println("\n" + arg + "\n");
}
// Those two flags will not create a successful LingeredApp.
private static String[] noDumpFlags =
{"-XX:+DumpSharedSpaces",
"-Xshare:dump"};
// Those flags will be excluded in static dumping,
// See src/java.base/share/classes/jdk/internal/misc/CDS.java
private static String[] excludeFlags = {
"-XX:DumpLoadedClassList=AnyFileName.classlist",
// this flag just dump archive, won't run app normally.
// "-XX:+DumpSharedSpaces",
"-XX:+DynamicDumpSharedSpaces",
"-XX:+RecordDynamicDumpInfo",
"-Xshare:on",
"-Xshare:auto",
"-XX:SharedClassListFile=non-exist.classlist",
"-XX:SharedArchiveFile=non-exist.jsa",
"-XX:ArchiveClassesAtExit=tmp.jsa",
"-XX:+UseSharedSpaces",
"-XX:+RequireSharedSpaces"};
// Times to dump cds against same process.
private static final int ITERATION_TIMES = 2;
private static void test() throws Exception {
LingeredApp app = null;
long pid;
int test_count = 1;
final boolean useBoot = true;
final boolean noBoot = !useBoot;
final boolean EXPECT_PASS = true;
final boolean EXPECT_FAIL = !EXPECT_PASS;
// Static dump with default name multiple times.
print2ln(test_count++ + " Static dump with default name multiple times.");
app = createLingeredApp("-cp", allJars);
pid = app.getPid();
for (int i = 0; i < ITERATION_TIMES; i++) {
test(SUBCMD_STATIC_DUMP, null, pid, noBoot, EXPECT_PASS, STATIC_MESSAGES);
}
app.stopApp();
// Test static dump with given file name.
print2ln(test_count++ + " Test static dump with given file name.");
app = createLingeredApp("-cp", allJars);
pid = app.getPid();
for (int i = 0; i < ITERATION_TIMES; i++) {
test(SUBCMD_STATIC_DUMP, STATIC_DUMP_FILE + "0" + i + ".jsa", pid, noBoot, EXPECT_PASS, STATIC_MESSAGES);
}
app.stopApp();
// Test static dump with flags with which dumping should fail
// This test will result classes.jsa in default server dir if -XX:SharedArchiveFile= not set.
print2ln(test_count++ + " Test static dump with flags with which dumping should fail.");
for (String flag : noDumpFlags) {
app = createLingeredApp("-cp", allJars, flag, "-XX:SharedArchiveFile=tmp.jsa");
// Following should not be executed.
if (app != null && app.getProcess().isAlive()) {
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, noBoot, EXPECT_FAIL);
app.stopApp();
// if above executed OK, mean failed.
throw new RuntimeException("Should not dump successful with " + flag);
}
}
// Test static with -Xbootclasspath/a:boot.jar
print2ln(test_count++ + " Test static with -Xbootassath/a:boot.jar");
app = createLingeredApp("-Xbootclasspath/a:" + bootJar, "-cp", testJar);
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, useBoot, EXPECT_PASS, STATIC_MESSAGES);
app.stopApp();
// Test static with limit-modules java.base.
print2ln(test_count++ + " Test static with --limit-modules java.base.");
app = createLingeredApp("--limit-modules", "java.base", "-cp", allJars);
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, noBoot, EXPECT_FAIL);
app.stopApp();
// Test static dump with flags which will be filtered before dumping.
print2ln(test_count++ + " Test static dump with flags which will be filtered before dumping.");
for (String flag : excludeFlags) {
app = createLingeredApp("-cp", allJars, flag);
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, noBoot, EXPECT_PASS, STATIC_MESSAGES);
app.stopApp();
}
// Test static with -Xshare:off will be OK to dump.
print2ln(test_count++ + " Test static with -Xshare:off will be OK to dump.");
app = createLingeredApp("-Xshare:off", "-cp", allJars);
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, noBoot, EXPECT_PASS, STATIC_MESSAGES);
app.stopApp();
// Test dynamic dump with -XX:+RecordDynamicDumpInfo.
print2ln(test_count++ + " Test dynamic dump with -XX:+RecordDynamicDumpInfo.");
app = createLingeredApp("-cp", allJars, "-XX:+RecordDynamicDumpInfo");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, DYNAMIC_DUMP_FILE + "01.jsa", pid, noBoot, EXPECT_PASS, DYNAMIC_MESSAGES);
// Test dynamic dump twice to same process.
print2ln(test_count++ + " Test dynamic dump second time to the same process.");
test(SUBCMD_DYNAMIC_DUMP, DYNAMIC_DUMP_FILE + "02.jsa", pid, noBoot, EXPECT_FAIL);
app.stopApp();
// Test dynamic dump with -XX:-RecordDynamicDumpInfo.
print2ln(test_count++ + " Test dynamic dump with -XX:-RecordDynamicDumpInfo.");
app = createLingeredApp("-cp", allJars);
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, DYNAMIC_DUMP_FILE + "01.jsa", pid, noBoot, EXPECT_FAIL);
app.stopApp();
// Test dynamic dump with default archive name (null).
print2ln(test_count++ + " Test dynamic dump with default archive name (null).");
app = createLingeredApp("-cp", allJars, "-XX:+RecordDynamicDumpInfo");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, null, pid, noBoot, EXPECT_PASS, DYNAMIC_MESSAGES);
app.stopApp();
// Test dynamic dump with flags -XX:+RecordDynamicDumpInfo -XX:-DynamicDumpSharedSpaces.
print2ln(test_count++ + " Test dynamic dump with flags -XX:+RecordDynamicDumpInfo -XX:-DynamicDumpSharedSpaces.");
app = createLingeredApp("-cp", allJars, "-XX:+RecordDynamicDumpInfo", "-XX:-DynamicDumpSharedSpaces");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, null, pid, noBoot, EXPECT_PASS, DYNAMIC_MESSAGES);
app.stopApp();
// Test dynamic dump with flags -XX:-DynamicDumpSharedSpaces -XX:+RecordDynamicDumpInfo.
print2ln(test_count++ + " Test dynamic dump with flags -XX:-DynamicDumpSharedSpaces -XX:+RecordDynamicDumpInfo.");
app = createLingeredApp("-cp", allJars, "-XX:-DynamicDumpSharedSpaces", "-XX:+RecordDynamicDumpInfo");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, null, pid, noBoot, EXPECT_PASS, DYNAMIC_MESSAGES);
app.stopApp();
// Test dynamic with -Xbootclasspath/a:boot.jar
print2ln(test_count++ + " Test dynamic with -Xbootclasspath/a:boot.jar");
app = createLingeredApp("-cp", testJar, "-Xbootclasspath/a:" + bootJar, "-XX:+RecordDynamicDumpInfo");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, null, pid, useBoot, EXPECT_PASS, DYNAMIC_MESSAGES);
app.stopApp();
// Test dynamic dump with -XX:ArchiveClassAtExit will fail.
print2ln(test_count++ + " Test dynamic dump with -XX:ArchiveClassAtExit will fail.");
app = createLingeredApp("-cp", allJars,
"-Xshare:auto",
"-XX:+RecordDynamicDumpInfo",
"-XX:ArchiveClassesAtExit=AnyName.jsa");
if (app != null) {
if (app.getProcess().isAlive()) {
throw new RuntimeException("The JCmdTestLingeredApp should not start up!");
}
}
}
public static void main(String... args) throws Exception {
boolean cdsEnabled = WhiteBox.getWhiteBox().getBooleanVMFlag("UseSharedSpaces");
if (!cdsEnabled) {
throw new SkippedException("CDS is not available for this JDK.");
}
buildJar();
test();
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021, 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.
*/
import jdk.test.lib.apps.LingeredApp;
public class JCmdTestLingeredApp extends LingeredApp {
public JCmdTestLingeredApp() {
// Do not use default test.class.path in class path.
setUseDefaultClasspath(false);
}
public static void main(String args[]) {
try {
Class.forName("Hello");
} catch (Exception e) {
System.out.print("Could not load Hello "+ e);
}
LingeredApp.main(args);
}
}

View file

@ -24,7 +24,9 @@
package jdk.test.lib.apps;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@ -89,12 +91,14 @@ public class LingeredApp {
private Thread outPumperThread;
private Thread errPumperThread;
private boolean finishAppCalled = false;
private boolean useDefaultClasspath = true;
protected Process appProcess;
protected OutputBuffer output;
protected static final int appWaitTime = 100;
protected static final int appCoreWaitTime = 480;
protected final String lockFileName;
protected String logFileName;
protected boolean forceCrash = false; // set true to force a crash and core file
@ -127,6 +131,10 @@ public class LingeredApp {
return this.lockFileName;
}
public void setLogFileName(String name) {
logFileName = name;
}
/**
*
* @return pid of java process running testapp
@ -310,10 +318,12 @@ public class LingeredApp {
cmd.add("-Djava.library.path=" + System.getProperty("java.library.path"));
}
// Make sure we set correct classpath to run the app
cmd.add("-cp");
String classpath = System.getProperty("test.class.path");
cmd.add((classpath == null) ? "." : classpath);
if (useDefaultClasspath()) {
// Make sure we set correct classpath to run the app
cmd.add("-cp");
String classpath = System.getProperty("test.class.path");
cmd.add((classpath == null) ? "." : classpath);
}
return cmd;
}
@ -336,6 +346,9 @@ public class LingeredApp {
.collect(Collectors.joining(" ", "Command line: [", "]")));
}
public boolean useDefaultClasspath() { return useDefaultClasspath; }
public void setUseDefaultClasspath(boolean value) { useDefaultClasspath = value; }
/**
* Run the app.
* User should provide exact options to run app. Might use #Utils.getTestJavaOpts() to set default test options.
@ -379,7 +392,19 @@ public class LingeredApp {
" LingeredApp stderr: [" + output.getStderr() + "]\n" +
" LingeredApp exitValue = " + appProcess.exitValue();
System.out.println(msg);
if (logFileName != null) {
System.out.println(" LingeredApp exitValue = " + appProcess.exitValue());
System.out.println(" LingeredApp output: " + logFileName + " (" + msg.length() + " chars)");
try (FileOutputStream fos = new FileOutputStream(logFileName);
PrintStream ps = new PrintStream(fos);) {
ps.print(msg);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
} else {
System.out.println(msg);
}
}
}