mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 23:34:52 +02:00
8259070: Add jcmd option to dump CDS
Reviewed-by: ccheung, iklam, mli
This commit is contained in:
parent
593194864a
commit
e7cbeba866
23 changed files with 800 additions and 75 deletions
|
@ -52,6 +52,8 @@ JVM_DefineClass
|
||||||
JVM_DefineClassWithSource
|
JVM_DefineClassWithSource
|
||||||
JVM_DesiredAssertionStatus
|
JVM_DesiredAssertionStatus
|
||||||
JVM_DumpAllStacks
|
JVM_DumpAllStacks
|
||||||
|
JVM_DumpClassListToFile
|
||||||
|
JVM_DumpDynamicArchive
|
||||||
JVM_DumpThreads
|
JVM_DumpThreads
|
||||||
JVM_FillInStackTrace
|
JVM_FillInStackTrace
|
||||||
JVM_FindClassFromCaller
|
JVM_FindClassFromCaller
|
||||||
|
|
|
@ -777,19 +777,6 @@ ClassPathZipEntry* ClassLoader::create_class_path_zip_entry(const char *path, bo
|
||||||
return NULL;
|
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.
|
// The boot append entries are added with a lock, and read lock free.
|
||||||
void ClassLoader::add_to_boot_append_entries(ClassPathEntry *new_entry) {
|
void ClassLoader::add_to_boot_append_entries(ClassPathEntry *new_entry) {
|
||||||
if (new_entry != NULL) {
|
if (new_entry != NULL) {
|
||||||
|
|
|
@ -385,9 +385,6 @@ class ClassLoader: AllStatic {
|
||||||
static jlong class_link_count();
|
static jlong class_link_count();
|
||||||
static jlong class_link_time_ms();
|
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
|
// adds a class path to the boot append entries
|
||||||
static void add_to_boot_append_entries(ClassPathEntry* new_entry);
|
static void add_to_boot_append_entries(ClassPathEntry* new_entry);
|
||||||
|
|
||||||
|
|
|
@ -300,6 +300,8 @@
|
||||||
template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \
|
template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \
|
||||||
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
||||||
template(generateLambdaFormHolderClasses_signature, "([Ljava/lang/String;)[Ljava/lang/Object;") \
|
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) */ \
|
/* Intrinsic Annotation (JDK 9 and above) */ \
|
||||||
template(jdk_internal_vm_annotation_DontInline_signature, "Ljdk/internal/vm/annotation/DontInline;") \
|
template(jdk_internal_vm_annotation_DontInline_signature, "Ljdk/internal/vm/annotation/DontInline;") \
|
||||||
|
|
|
@ -200,6 +200,12 @@ JVM_GetRandomSeedForDumping();
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
JVM_LogLambdaFormInvoker(JNIEnv* env, jstring line);
|
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
|
* java.lang.Throwable
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "classfile/classLoaderData.inline.hpp"
|
#include "classfile/classLoaderData.inline.hpp"
|
||||||
#include "classfile/symbolTable.hpp"
|
#include "classfile/symbolTable.hpp"
|
||||||
#include "classfile/systemDictionaryShared.hpp"
|
#include "classfile/systemDictionaryShared.hpp"
|
||||||
|
#include "classfile/vmSymbols.hpp"
|
||||||
#include "gc/shared/collectedHeap.hpp"
|
#include "gc/shared/collectedHeap.hpp"
|
||||||
#include "gc/shared/gcVMOperations.hpp"
|
#include "gc/shared/gcVMOperations.hpp"
|
||||||
#include "gc/shared/gc_globals.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() {
|
void DynamicArchive::dump() {
|
||||||
if (Arguments::GetSharedDynamicArchivePath() == NULL) {
|
if (Arguments::GetSharedDynamicArchivePath() == NULL) {
|
||||||
|
|
|
@ -58,8 +58,12 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
class DynamicArchive : AllStatic {
|
class DynamicArchive : AllStatic {
|
||||||
|
static bool _has_been_dumped_once;
|
||||||
public:
|
public:
|
||||||
|
static void dump(const char* archive_name, TRAPS);
|
||||||
static void dump();
|
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 is_mapped() { return FileMapInfo::dynamic_info() != NULL; }
|
||||||
static bool validate(FileMapInfo* dynamic_info);
|
static bool validate(FileMapInfo* dynamic_info);
|
||||||
};
|
};
|
||||||
|
|
|
@ -145,6 +145,33 @@ static bool shared_base_valid(char* shared_base) {
|
||||||
#endif
|
#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) {
|
static bool shared_base_too_high(char* specified_base, char* aligned_base, size_t cds_max) {
|
||||||
if (specified_base != NULL && aligned_base < specified_base) {
|
if (specified_base != NULL && aligned_base < specified_base) {
|
||||||
// SharedBaseAddress is very high (e.g., 0xffffffffffffff00) so
|
// SharedBaseAddress is very high (e.g., 0xffffffffffffff00) so
|
||||||
|
|
|
@ -143,6 +143,8 @@ public:
|
||||||
// (Heap region alignments are decided by GC).
|
// (Heap region alignments are decided by GC).
|
||||||
static size_t core_region_alignment();
|
static size_t core_region_alignment();
|
||||||
static void rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread* thread, InstanceKlass* ik);
|
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
|
#endif
|
||||||
|
|
||||||
// Allocate a block of memory from the temporary "symbol" region.
|
// Allocate a block of memory from the temporary "symbol" region.
|
||||||
|
|
|
@ -3672,7 +3672,7 @@ const char* InstanceKlass::internal_name() const {
|
||||||
void InstanceKlass::print_class_load_logging(ClassLoaderData* loader_data,
|
void InstanceKlass::print_class_load_logging(ClassLoaderData* loader_data,
|
||||||
const ModuleEntry* module_entry,
|
const ModuleEntry* module_entry,
|
||||||
const ClassFileStream* cfs) const {
|
const ClassFileStream* cfs) const {
|
||||||
log_to_classlist(cfs);
|
log_to_classlist();
|
||||||
|
|
||||||
if (!log_is_enabled(Info, class, load)) {
|
if (!log_is_enabled(Info, class, load)) {
|
||||||
return;
|
return;
|
||||||
|
@ -4265,53 +4265,37 @@ unsigned char * InstanceKlass::get_cached_class_file_bytes() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void InstanceKlass::log_to_classlist(const ClassFileStream* stream) const {
|
bool InstanceKlass::is_shareable() const {
|
||||||
#if INCLUDE_CDS
|
#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 (ClassListWriter::is_enabled()) {
|
||||||
if (!ClassLoader::has_jrt_entry()) {
|
if (!ClassLoader::has_jrt_entry()) {
|
||||||
warning("DumpLoadedClassList and CDS are not supported in exploded build");
|
warning("DumpLoadedClassList and CDS are not supported in exploded build");
|
||||||
DumpLoadedClassList = NULL;
|
DumpLoadedClassList = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClassLoaderData* loader_data = class_loader_data();
|
if (is_shareable()) {
|
||||||
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 {
|
|
||||||
ClassListWriter w;
|
ClassListWriter w;
|
||||||
w.stream()->print_cr("%s", name()->as_C_string());
|
w.stream()->print_cr("%s", name()->as_C_string());
|
||||||
w.stream()->flush();
|
w.stream()->flush();
|
||||||
|
|
|
@ -361,6 +361,9 @@ class InstanceKlass: public Klass {
|
||||||
return (_misc_flags & shared_loader_type_bits()) == 0;
|
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() {
|
void clear_shared_class_loader_type() {
|
||||||
_misc_flags &= ~shared_loader_type_bits();
|
_misc_flags &= ~shared_loader_type_bits();
|
||||||
}
|
}
|
||||||
|
@ -1261,7 +1264,7 @@ private:
|
||||||
void mark_newly_obsolete_methods(Array<Method*>* old_methods, int emcp_method_count);
|
void mark_newly_obsolete_methods(Array<Method*>* old_methods, int emcp_method_count);
|
||||||
#endif
|
#endif
|
||||||
// log class name to classlist
|
// log class name to classlist
|
||||||
void log_to_classlist(const ClassFileStream* cfs) const;
|
void log_to_classlist() const;
|
||||||
public:
|
public:
|
||||||
// CDS support - remove and restore oops from metadata. Oops are not shared.
|
// CDS support - remove and restore oops from metadata. Oops are not shared.
|
||||||
virtual void remove_unshareable_info();
|
virtual void remove_unshareable_info();
|
||||||
|
|
|
@ -3703,6 +3703,24 @@ JVM_ENTRY(void, JVM_LogLambdaFormInvoker(JNIEnv *env, jstring line))
|
||||||
#endif // INCLUDE_CDS
|
#endif // INCLUDE_CDS
|
||||||
JVM_END
|
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,
|
// Returns an array of all live Thread objects (VM internal JavaThreads,
|
||||||
// jvmti agent threads, and JNI attaching threads are skipped)
|
// jvmti agent threads, and JNI attaching threads are skipped)
|
||||||
// See CR 6404306 regarding JNI attaching threads
|
// See CR 6404306 regarding JNI attaching threads
|
||||||
|
|
|
@ -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.");
|
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) {
|
if (UseSharedSpaces && patch_mod_javabase) {
|
||||||
no_shared_spaces("CDS is disabled when " JAVA_BASE_NAME " module is patched.");
|
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();
|
check_unsupported_dumping_properties();
|
||||||
SharedDynamicArchivePath = os::strdup_check_oom(ArchiveClassesAtExit, mtArguments);
|
SharedDynamicArchivePath = os::strdup_check_oom(ArchiveClassesAtExit, mtArguments);
|
||||||
|
} else {
|
||||||
|
if (SharedDynamicArchivePath != nullptr) {
|
||||||
|
os::free(SharedDynamicArchivePath);
|
||||||
|
SharedDynamicArchivePath = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (SharedArchiveFile == NULL) {
|
if (SharedArchiveFile == NULL) {
|
||||||
SharedArchivePath = get_default_shared_archive_path();
|
SharedArchivePath = get_default_shared_archive_path();
|
||||||
|
|
|
@ -1889,6 +1889,9 @@ const intx ObjectAlignmentInBytes = 8;
|
||||||
product(bool, DynamicDumpSharedSpaces, false, \
|
product(bool, DynamicDumpSharedSpaces, false, \
|
||||||
"Dynamic archive") \
|
"Dynamic archive") \
|
||||||
\
|
\
|
||||||
|
product(bool, RecordDynamicDumpInfo, false, \
|
||||||
|
"Record class info for jcmd VM.cds dynamic_dump") \
|
||||||
|
\
|
||||||
product(bool, PrintSharedArchiveAndExit, false, \
|
product(bool, PrintSharedArchiveAndExit, false, \
|
||||||
"Print shared archive file contents") \
|
"Print shared archive file contents") \
|
||||||
\
|
\
|
||||||
|
|
|
@ -141,6 +141,9 @@ void DCmdRegistrant::register_dcmds(){
|
||||||
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<DebugOnCmdStartDCmd>(full_export, true, true));
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<DebugOnCmdStartDCmd>(full_export, true, true));
|
||||||
#endif // INCLUDE_JVMTI
|
#endif // INCLUDE_JVMTI
|
||||||
|
|
||||||
|
#if INCLUDE_CDS
|
||||||
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<DumpSharedArchiveDCmd>(full_export, true, false));
|
||||||
|
#endif // INCLUDE_CDS
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef HAVE_EXTRA_DCMD
|
#ifndef HAVE_EXTRA_DCMD
|
||||||
|
@ -924,6 +927,60 @@ void TouchedMethodsDCmd::execute(DCmdSource source, TRAPS) {
|
||||||
VM_DumpTouchedMethods dumper(output());
|
VM_DumpTouchedMethods dumper(output());
|
||||||
VMThread::execute(&dumper);
|
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
|
#if INCLUDE_JVMTI
|
||||||
extern "C" typedef char const* (JNICALL *debugInit_startDebuggingViaCommandPtr)(JNIEnv* env, jthread thread, char const** transport_name,
|
extern "C" typedef char const* (JNICALL *debugInit_startDebuggingViaCommandPtr)(JNIEnv* env, jthread thread, char const** transport_name,
|
||||||
char const** address, jboolean* first_start);
|
char const** address, jboolean* first_start);
|
||||||
|
|
|
@ -399,6 +399,32 @@ public:
|
||||||
virtual void execute(DCmdSource source, TRAPS);
|
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
|
// See also: thread_dump in attachListener.cpp
|
||||||
class ThreadDumpDCmd : public DCmdWithParser {
|
class ThreadDumpDCmd : public DCmdWithParser {
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -25,7 +25,15 @@
|
||||||
|
|
||||||
package jdk.internal.misc;
|
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.Arrays;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -63,6 +71,7 @@ public class CDS {
|
||||||
public static boolean isSharingEnabled() {
|
public static boolean isSharingEnabled() {
|
||||||
return isSharingEnabled;
|
return isSharingEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static native boolean isDumpingClassList0();
|
private static native boolean isDumpingClassList0();
|
||||||
private static native boolean isDumpingArchive0();
|
private static native boolean isDumpingArchive0();
|
||||||
private static native boolean isSharingEnabled0();
|
private static native boolean isSharingEnabled0();
|
||||||
|
@ -195,4 +204,123 @@ public class CDS {
|
||||||
};
|
};
|
||||||
return retArray;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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) {
|
Java_jdk_internal_misc_CDS_logLambdaFormInvoker(JNIEnv *env, jclass jcls, jstring line) {
|
||||||
JVM_LogLambdaFormInvoker(env, 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);
|
||||||
|
}
|
||||||
|
|
|
@ -324,6 +324,7 @@ hotspot_appcds_dynamic = \
|
||||||
-runtime/cds/appcds/javaldr/GCSharedStringsDuringDump.java \
|
-runtime/cds/appcds/javaldr/GCSharedStringsDuringDump.java \
|
||||||
-runtime/cds/appcds/javaldr/HumongousDuringDump.java \
|
-runtime/cds/appcds/javaldr/HumongousDuringDump.java \
|
||||||
-runtime/cds/appcds/javaldr/LockDuringDump.java \
|
-runtime/cds/appcds/javaldr/LockDuringDump.java \
|
||||||
|
-runtime/cds/appcds/jcmd/JCmdTest.java \
|
||||||
-runtime/cds/appcds/methodHandles \
|
-runtime/cds/appcds/methodHandles \
|
||||||
-runtime/cds/appcds/sharedStrings \
|
-runtime/cds/appcds/sharedStrings \
|
||||||
-runtime/cds/appcds/ArchiveRelocationTest.java \
|
-runtime/cds/appcds/ArchiveRelocationTest.java \
|
||||||
|
|
|
@ -83,10 +83,6 @@ public class DumpClassList {
|
||||||
appClass[0])
|
appClass[0])
|
||||||
.assertNormalExit(output -> {
|
.assertNormalExit(output -> {
|
||||||
output.shouldContain("hello world");
|
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())
|
CDSOptions opts = (new CDSOptions())
|
||||||
|
|
362
test/hotspot/jtreg/runtime/cds/appcds/jcmd/JCmdTest.java
Normal file
362
test/hotspot/jtreg/runtime/cds/appcds/jcmd/JCmdTest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,9 @@
|
||||||
package jdk.test.lib.apps;
|
package jdk.test.lib.apps;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -89,12 +91,14 @@ public class LingeredApp {
|
||||||
private Thread outPumperThread;
|
private Thread outPumperThread;
|
||||||
private Thread errPumperThread;
|
private Thread errPumperThread;
|
||||||
private boolean finishAppCalled = false;
|
private boolean finishAppCalled = false;
|
||||||
|
private boolean useDefaultClasspath = true;
|
||||||
|
|
||||||
protected Process appProcess;
|
protected Process appProcess;
|
||||||
protected OutputBuffer output;
|
protected OutputBuffer output;
|
||||||
protected static final int appWaitTime = 100;
|
protected static final int appWaitTime = 100;
|
||||||
protected static final int appCoreWaitTime = 480;
|
protected static final int appCoreWaitTime = 480;
|
||||||
protected final String lockFileName;
|
protected final String lockFileName;
|
||||||
|
protected String logFileName;
|
||||||
|
|
||||||
protected boolean forceCrash = false; // set true to force a crash and core file
|
protected boolean forceCrash = false; // set true to force a crash and core file
|
||||||
|
|
||||||
|
@ -127,6 +131,10 @@ public class LingeredApp {
|
||||||
return this.lockFileName;
|
return this.lockFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLogFileName(String name) {
|
||||||
|
logFileName = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return pid of java process running testapp
|
* @return pid of java process running testapp
|
||||||
|
@ -310,10 +318,12 @@ public class LingeredApp {
|
||||||
cmd.add("-Djava.library.path=" + System.getProperty("java.library.path"));
|
cmd.add("-Djava.library.path=" + System.getProperty("java.library.path"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useDefaultClasspath()) {
|
||||||
// Make sure we set correct classpath to run the app
|
// Make sure we set correct classpath to run the app
|
||||||
cmd.add("-cp");
|
cmd.add("-cp");
|
||||||
String classpath = System.getProperty("test.class.path");
|
String classpath = System.getProperty("test.class.path");
|
||||||
cmd.add((classpath == null) ? "." : classpath);
|
cmd.add((classpath == null) ? "." : classpath);
|
||||||
|
}
|
||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
@ -336,6 +346,9 @@ public class LingeredApp {
|
||||||
.collect(Collectors.joining(" ", "Command line: [", "]")));
|
.collect(Collectors.joining(" ", "Command line: [", "]")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean useDefaultClasspath() { return useDefaultClasspath; }
|
||||||
|
public void setUseDefaultClasspath(boolean value) { useDefaultClasspath = value; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the app.
|
* Run the app.
|
||||||
* User should provide exact options to run app. Might use #Utils.getTestJavaOpts() to set default test options.
|
* User should provide exact options to run app. Might use #Utils.getTestJavaOpts() to set default test options.
|
||||||
|
@ -379,9 +392,21 @@ public class LingeredApp {
|
||||||
" LingeredApp stderr: [" + output.getStderr() + "]\n" +
|
" LingeredApp stderr: [" + output.getStderr() + "]\n" +
|
||||||
" LingeredApp exitValue = " + appProcess.exitValue();
|
" LingeredApp exitValue = " + appProcess.exitValue();
|
||||||
|
|
||||||
|
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);
|
System.out.println(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete lock file that signals app to terminate, then
|
* Delete lock file that signals app to terminate, then
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue