Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8259070: Add jcmd option to dump CDS #2737

Closed
wants to merge 17 commits into from
Closed
Changes from all commits
Commits
File filter
Filter file types
Jump to
Jump to file
Failed to load files.

Always

Just for now

@@ -52,6 +52,8 @@ JVM_DefineClass
JVM_DefineClassWithSource
JVM_DesiredAssertionStatus
JVM_DumpAllStacks
JVM_DumpClassListToFile
JVM_DumpDynamicArchive
JVM_DumpThreads
JVM_FillInStackTrace
JVM_FindClassFromCaller
@@ -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) {
@@ -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);

@@ -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;") \
@@ -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
*/
@@ -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 @@ class VM_PopulateDynamicDumpSharedSpace: public VM_GC_Sync_Operation {
}
};

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) {
@@ -58,8 +58,12 @@ class DynamicArchiveHeader : public FileMapHeader {
};

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);
};
@@ -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
@@ -143,6 +143,8 @@ class MetaspaceShared : AllStatic {
// (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.
@@ -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())) {
This conversation was marked as resolved by yminqi

This comment has been minimized.

@calvinccheung

calvinccheung Mar 24, 2021
Member

Since the above has been removed, I think the ClassLoader::contains_append_entry() function can be removed too.

// .. 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();
@@ -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 @@ class InstanceKlass: public Klass {
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();
@@ -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());
This conversation was marked as resolved by yminqi

This comment has been minimized.

@iklam

iklam Mar 31, 2021
Member

A ResourceMark is needed before calling java_lang_String::as_utf8_string().

In general, I think the code in jvm.cpp should only marshall the jobject argument (e.g., convert jstring to char*.). The main functionality of JVM_DumpDynamicArchive should be moved to dynamicArchive.cpp. Similarly, most of the work in JVM_DumpClassListToFile should be moved to metaspaceShared.cpp.

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
@@ -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) {

// 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);
This conversation was marked as resolved by yminqi

This comment has been hidden.

@iklam

iklam Feb 26, 2021
Member

I think this will be more readable:

if (ArchiveClassesAtExit != NULL || RecordDynamicDumpInfo) {
    FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, true);
} else {
    FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, false);

BTW, what happens if you specify -XX:-DynamicDumpSharedSpaces -XX:+RecordDynamicDumpInfo?

}

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;
}
This conversation was marked as resolved by yminqi

This comment has been minimized.

@iklam

iklam Feb 26, 2021
Member

Is this necessary?

This comment has been minimized.

@yminqi

yminqi Mar 18, 2021
Author Contributor

When do dynamic dump, we set SharedDynamicArchivePath to the given file name, after that, restore the original value so free the old one to prevent memory leak.

}
if (SharedArchiveFile == NULL) {
SharedArchivePath = get_default_shared_archive_path();
@@ -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") \
\
ProTip! Use n and p to navigate between commits in a pull request.