mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
8293182: Improve testing of CDS archive heap
Reviewed-by: ccheung, coleenp
This commit is contained in:
parent
51de765867
commit
f84386cf6e
11 changed files with 594 additions and 81 deletions
|
@ -71,6 +71,11 @@
|
||||||
product(bool, AllowArchivingWithJavaAgent, false, DIAGNOSTIC, \
|
product(bool, AllowArchivingWithJavaAgent, false, DIAGNOSTIC, \
|
||||||
"Allow Java agent to be run with CDS dumping") \
|
"Allow Java agent to be run with CDS dumping") \
|
||||||
\
|
\
|
||||||
|
develop(ccstr, ArchiveHeapTestClass, NULL, \
|
||||||
|
"For JVM internal testing only. The static field named " \
|
||||||
|
"\"archivedObjects\" of the specified class is stored in the " \
|
||||||
|
"CDS archive heap") \
|
||||||
|
\
|
||||||
product(ccstr, DumpLoadedClassList, NULL, \
|
product(ccstr, DumpLoadedClassList, NULL, \
|
||||||
"Dump the names all loaded classes, that could be stored into " \
|
"Dump the names all loaded classes, that could be stored into " \
|
||||||
"the CDS archive, in the specified file") \
|
"the CDS archive, in the specified file") \
|
||||||
|
|
|
@ -65,9 +65,33 @@
|
||||||
|
|
||||||
#if INCLUDE_CDS_JAVA_HEAP
|
#if INCLUDE_CDS_JAVA_HEAP
|
||||||
|
|
||||||
|
struct ArchivableStaticFieldInfo {
|
||||||
|
const char* klass_name;
|
||||||
|
const char* field_name;
|
||||||
|
InstanceKlass* klass;
|
||||||
|
int offset;
|
||||||
|
BasicType type;
|
||||||
|
|
||||||
|
ArchivableStaticFieldInfo(const char* k, const char* f)
|
||||||
|
: klass_name(k), field_name(f), klass(NULL), offset(0), type(T_ILLEGAL) {}
|
||||||
|
|
||||||
|
bool valid() {
|
||||||
|
return klass_name != NULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool HeapShared::_disable_writing = false;
|
bool HeapShared::_disable_writing = false;
|
||||||
DumpedInternedStrings *HeapShared::_dumped_interned_strings = NULL;
|
DumpedInternedStrings *HeapShared::_dumped_interned_strings = NULL;
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
#define ARCHIVE_TEST_FIELD_NAME "archivedObjects"
|
||||||
|
static Array<char>* _archived_ArchiveHeapTestClass = NULL;
|
||||||
|
static const char* _test_class_name = NULL;
|
||||||
|
static const Klass* _test_class = NULL;
|
||||||
|
static const ArchivedKlassSubGraphInfoRecord* _test_class_record = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// If you add new entries to the following tables, you should know what you're doing!
|
// If you add new entries to the following tables, you should know what you're doing!
|
||||||
//
|
//
|
||||||
|
@ -83,6 +107,7 @@ static ArchivableStaticFieldInfo closed_archive_subgraph_entry_fields[] = {
|
||||||
{"java/lang/Character$CharacterCache", "archivedCache"},
|
{"java/lang/Character$CharacterCache", "archivedCache"},
|
||||||
{"java/util/jar/Attributes$Name", "KNOWN_NAMES"},
|
{"java/util/jar/Attributes$Name", "KNOWN_NAMES"},
|
||||||
{"sun/util/locale/BaseLocale", "constantBaseLocales"},
|
{"sun/util/locale/BaseLocale", "constantBaseLocales"},
|
||||||
|
{NULL, NULL},
|
||||||
};
|
};
|
||||||
// Entry fields for subgraphs archived in the open archive heap region.
|
// Entry fields for subgraphs archived in the open archive heap region.
|
||||||
static ArchivableStaticFieldInfo open_archive_subgraph_entry_fields[] = {
|
static ArchivableStaticFieldInfo open_archive_subgraph_entry_fields[] = {
|
||||||
|
@ -91,6 +116,10 @@ static ArchivableStaticFieldInfo open_archive_subgraph_entry_fields[] = {
|
||||||
{"java/lang/ModuleLayer", "EMPTY_LAYER"},
|
{"java/lang/ModuleLayer", "EMPTY_LAYER"},
|
||||||
{"java/lang/module/Configuration", "EMPTY_CONFIGURATION"},
|
{"java/lang/module/Configuration", "EMPTY_CONFIGURATION"},
|
||||||
{"jdk/internal/math/FDBigInteger", "archivedCaches"},
|
{"jdk/internal/math/FDBigInteger", "archivedCaches"},
|
||||||
|
#ifndef PRODUCT
|
||||||
|
{NULL, NULL}, // Extra slot for -XX:ArchiveHeapTestClass
|
||||||
|
#endif
|
||||||
|
{NULL, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Entry fields for subgraphs archived in the open archive heap region (full module graph).
|
// Entry fields for subgraphs archived in the open archive heap region (full module graph).
|
||||||
|
@ -98,15 +127,9 @@ static ArchivableStaticFieldInfo fmg_open_archive_subgraph_entry_fields[] = {
|
||||||
{"jdk/internal/loader/ArchivedClassLoaders", "archivedClassLoaders"},
|
{"jdk/internal/loader/ArchivedClassLoaders", "archivedClassLoaders"},
|
||||||
{"jdk/internal/module/ArchivedBootLayer", "archivedBootLayer"},
|
{"jdk/internal/module/ArchivedBootLayer", "archivedBootLayer"},
|
||||||
{"java/lang/Module$ArchivedData", "archivedData"},
|
{"java/lang/Module$ArchivedData", "archivedData"},
|
||||||
|
{NULL, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
const static int num_closed_archive_subgraph_entry_fields =
|
|
||||||
sizeof(closed_archive_subgraph_entry_fields) / sizeof(ArchivableStaticFieldInfo);
|
|
||||||
const static int num_open_archive_subgraph_entry_fields =
|
|
||||||
sizeof(open_archive_subgraph_entry_fields) / sizeof(ArchivableStaticFieldInfo);
|
|
||||||
const static int num_fmg_open_archive_subgraph_entry_fields =
|
|
||||||
sizeof(fmg_open_archive_subgraph_entry_fields) / sizeof(ArchivableStaticFieldInfo);
|
|
||||||
|
|
||||||
GrowableArrayCHeap<oop, mtClassShared>* HeapShared::_pending_roots = NULL;
|
GrowableArrayCHeap<oop, mtClassShared>* HeapShared::_pending_roots = NULL;
|
||||||
OopHandle HeapShared::_roots;
|
OopHandle HeapShared::_roots;
|
||||||
|
|
||||||
|
@ -118,8 +141,8 @@ bool HeapShared::is_archived_object_during_dumptime(oop p) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], int num, InstanceKlass* ik) {
|
static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], InstanceKlass* ik) {
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; fields[i].valid(); i++) {
|
||||||
if (fields[i].klass == ik) {
|
if (fields[i].klass == ik) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -128,12 +151,9 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], int nu
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) {
|
bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) {
|
||||||
return is_subgraph_root_class_of(closed_archive_subgraph_entry_fields,
|
return is_subgraph_root_class_of(closed_archive_subgraph_entry_fields, ik) ||
|
||||||
num_closed_archive_subgraph_entry_fields, ik) ||
|
is_subgraph_root_class_of(open_archive_subgraph_entry_fields, ik) ||
|
||||||
is_subgraph_root_class_of(open_archive_subgraph_entry_fields,
|
is_subgraph_root_class_of(fmg_open_archive_subgraph_entry_fields, ik);
|
||||||
num_open_archive_subgraph_entry_fields, ik) ||
|
|
||||||
is_subgraph_root_class_of(fmg_open_archive_subgraph_entry_fields,
|
|
||||||
num_fmg_open_archive_subgraph_entry_fields, ik);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned HeapShared::oop_hash(oop const& p) {
|
unsigned HeapShared::oop_hash(oop const& p) {
|
||||||
|
@ -477,7 +497,6 @@ void HeapShared::copy_closed_objects(GrowableArray<MemRegion>* closed_regions) {
|
||||||
StringTable::write_to_archive(_dumped_interned_strings);
|
StringTable::write_to_archive(_dumped_interned_strings);
|
||||||
|
|
||||||
archive_object_subgraphs(closed_archive_subgraph_entry_fields,
|
archive_object_subgraphs(closed_archive_subgraph_entry_fields,
|
||||||
num_closed_archive_subgraph_entry_fields,
|
|
||||||
true /* is_closed_archive */,
|
true /* is_closed_archive */,
|
||||||
false /* is_full_module_graph */);
|
false /* is_full_module_graph */);
|
||||||
|
|
||||||
|
@ -495,12 +514,10 @@ void HeapShared::copy_open_objects(GrowableArray<MemRegion>* open_regions) {
|
||||||
archive_klass_objects();
|
archive_klass_objects();
|
||||||
|
|
||||||
archive_object_subgraphs(open_archive_subgraph_entry_fields,
|
archive_object_subgraphs(open_archive_subgraph_entry_fields,
|
||||||
num_open_archive_subgraph_entry_fields,
|
|
||||||
false /* is_closed_archive */,
|
false /* is_closed_archive */,
|
||||||
false /* is_full_module_graph */);
|
false /* is_full_module_graph */);
|
||||||
if (MetaspaceShared::use_full_module_graph()) {
|
if (MetaspaceShared::use_full_module_graph()) {
|
||||||
archive_object_subgraphs(fmg_open_archive_subgraph_entry_fields,
|
archive_object_subgraphs(fmg_open_archive_subgraph_entry_fields,
|
||||||
num_fmg_open_archive_subgraph_entry_fields,
|
|
||||||
false /* is_closed_archive */,
|
false /* is_closed_archive */,
|
||||||
true /* is_full_module_graph */);
|
true /* is_full_module_graph */);
|
||||||
ClassLoaderDataShared::init_archived_oops();
|
ClassLoaderDataShared::init_archived_oops();
|
||||||
|
@ -612,11 +629,13 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) {
|
||||||
// to the sub-graph object class list.
|
// to the sub-graph object class list.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
check_allowed_klass(InstanceKlass::cast(orig_k));
|
||||||
} else if (relocated_k->is_objArray_klass()) {
|
} else if (relocated_k->is_objArray_klass()) {
|
||||||
Klass* abk = ObjArrayKlass::cast(relocated_k)->bottom_klass();
|
Klass* abk = ObjArrayKlass::cast(relocated_k)->bottom_klass();
|
||||||
if (abk->is_instance_klass()) {
|
if (abk->is_instance_klass()) {
|
||||||
assert(InstanceKlass::cast(abk)->is_shared_boot_class(),
|
assert(InstanceKlass::cast(abk)->is_shared_boot_class(),
|
||||||
"must be boot class");
|
"must be boot class");
|
||||||
|
check_allowed_klass(InstanceKlass::cast(ObjArrayKlass::cast(orig_k)->bottom_klass()));
|
||||||
}
|
}
|
||||||
if (relocated_k == Universe::objectArrayKlassObj()) {
|
if (relocated_k == Universe::objectArrayKlassObj()) {
|
||||||
// Initialized early during Universe::genesis. No need to be added
|
// Initialized early during Universe::genesis. No need to be added
|
||||||
|
@ -640,6 +659,28 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) {
|
||||||
_has_non_early_klasses |= is_non_early_klass(orig_k);
|
_has_non_early_klasses |= is_non_early_klass(orig_k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) {
|
||||||
|
if (ik->module()->name() == vmSymbols::java_base()) {
|
||||||
|
assert(ik->package() != NULL, "classes in java.base cannot be in unnamed package");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
if (!ik->module()->is_named() && ik->package() == NULL) {
|
||||||
|
// This class is loaded by ArchiveHeapTestClass
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char* extra_msg = ", or in an unnamed package of an unnamed module";
|
||||||
|
#else
|
||||||
|
const char* extra_msg = "";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ResourceMark rm;
|
||||||
|
log_error(cds, heap)("Class %s not allowed in archive heap. Must be in java.base%s",
|
||||||
|
ik->external_name(), extra_msg);
|
||||||
|
os::_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
bool KlassSubGraphInfo::is_non_early_klass(Klass* k) {
|
bool KlassSubGraphInfo::is_non_early_klass(Klass* k) {
|
||||||
if (k->is_objArray_klass()) {
|
if (k->is_objArray_klass()) {
|
||||||
k = ObjArrayKlass::cast(k)->bottom_klass();
|
k = ObjArrayKlass::cast(k)->bottom_klass();
|
||||||
|
@ -753,6 +794,15 @@ void HeapShared::write_subgraph_info_table() {
|
||||||
CopyKlassSubGraphInfoToArchive copy(&writer);
|
CopyKlassSubGraphInfoToArchive copy(&writer);
|
||||||
d_table->iterate(©);
|
d_table->iterate(©);
|
||||||
writer.dump(&_run_time_subgraph_info_table, "subgraphs");
|
writer.dump(&_run_time_subgraph_info_table, "subgraphs");
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
if (ArchiveHeapTestClass != NULL) {
|
||||||
|
size_t len = strlen(ArchiveHeapTestClass) + 1;
|
||||||
|
Array<char>* array = ArchiveBuilder::new_ro_array<char>((int)len);
|
||||||
|
strncpy(array->adr_at(0), ArchiveHeapTestClass, len);
|
||||||
|
_archived_ArchiveHeapTestClass = array;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeapShared::serialize(SerializeClosure* soc) {
|
void HeapShared::serialize(SerializeClosure* soc) {
|
||||||
|
@ -772,6 +822,14 @@ void HeapShared::serialize(SerializeClosure* soc) {
|
||||||
soc->do_oop(&roots_oop); // write to archive
|
soc->do_oop(&roots_oop); // write to archive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
soc->do_ptr((void**)&_archived_ArchiveHeapTestClass);
|
||||||
|
if (soc->reading() && _archived_ArchiveHeapTestClass != NULL) {
|
||||||
|
_test_class_name = _archived_ArchiveHeapTestClass->adr_at(0);
|
||||||
|
setup_test_class(_test_class_name);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
_run_time_subgraph_info_table.serialize_header(soc);
|
_run_time_subgraph_info_table.serialize_header(soc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -809,23 +867,18 @@ static void verify_the_heap(Klass* k, const char* which) {
|
||||||
// ClassFileLoadHook is enabled, it's possible for this class to be dynamically replaced. In
|
// ClassFileLoadHook is enabled, it's possible for this class to be dynamically replaced. In
|
||||||
// this case, we will not load the ArchivedKlassSubGraphInfoRecord and will clear its roots.
|
// this case, we will not load the ArchivedKlassSubGraphInfoRecord and will clear its roots.
|
||||||
void HeapShared::resolve_classes(JavaThread* THREAD) {
|
void HeapShared::resolve_classes(JavaThread* THREAD) {
|
||||||
|
assert(UseSharedSpaces, "runtime only!");
|
||||||
if (!ArchiveHeapLoader::is_fully_available()) {
|
if (!ArchiveHeapLoader::is_fully_available()) {
|
||||||
return; // nothing to do
|
return; // nothing to do
|
||||||
}
|
}
|
||||||
resolve_classes_for_subgraphs(closed_archive_subgraph_entry_fields,
|
resolve_classes_for_subgraphs(closed_archive_subgraph_entry_fields, THREAD);
|
||||||
num_closed_archive_subgraph_entry_fields,
|
resolve_classes_for_subgraphs(open_archive_subgraph_entry_fields, THREAD);
|
||||||
THREAD);
|
resolve_classes_for_subgraphs(fmg_open_archive_subgraph_entry_fields, THREAD);
|
||||||
resolve_classes_for_subgraphs(open_archive_subgraph_entry_fields,
|
|
||||||
num_open_archive_subgraph_entry_fields,
|
|
||||||
THREAD);
|
|
||||||
resolve_classes_for_subgraphs(fmg_open_archive_subgraph_entry_fields,
|
|
||||||
num_fmg_open_archive_subgraph_entry_fields,
|
|
||||||
THREAD);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeapShared::resolve_classes_for_subgraphs(ArchivableStaticFieldInfo fields[],
|
void HeapShared::resolve_classes_for_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
int num, JavaThread* THREAD) {
|
JavaThread* THREAD) {
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; fields[i].valid(); i++) {
|
||||||
ArchivableStaticFieldInfo* info = &fields[i];
|
ArchivableStaticFieldInfo* info = &fields[i];
|
||||||
TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name);
|
TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name);
|
||||||
InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name);
|
InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name);
|
||||||
|
@ -878,6 +931,13 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP
|
||||||
unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary_quick(k);
|
unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary_quick(k);
|
||||||
const ArchivedKlassSubGraphInfoRecord* record = _run_time_subgraph_info_table.lookup(k, hash, 0);
|
const ArchivedKlassSubGraphInfoRecord* record = _run_time_subgraph_info_table.lookup(k, hash, 0);
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
if (_test_class_name != NULL && k->name()->equals(_test_class_name) && record != NULL) {
|
||||||
|
_test_class = k;
|
||||||
|
_test_class_record = record;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialize from archived data. Currently this is done only
|
// Initialize from archived data. Currently this is done only
|
||||||
// during VM initialization time. No lock is needed.
|
// during VM initialization time. No lock is needed.
|
||||||
if (record != NULL) {
|
if (record != NULL) {
|
||||||
|
@ -899,6 +959,11 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log_is_enabled(Info, cds, heap)) {
|
||||||
|
ResourceMark rm;
|
||||||
|
log_info(cds, heap)("%s subgraph %s ", do_init ? "init" : "resolve", k->external_name());
|
||||||
|
}
|
||||||
|
|
||||||
resolve_or_init(k, do_init, CHECK_NULL);
|
resolve_or_init(k, do_init, CHECK_NULL);
|
||||||
|
|
||||||
// Load/link/initialize the klasses of the objects in the subgraph.
|
// Load/link/initialize the klasses of the objects in the subgraph.
|
||||||
|
@ -1400,10 +1465,11 @@ public:
|
||||||
|
|
||||||
virtual void do_field(fieldDescriptor* fd) {
|
virtual void do_field(fieldDescriptor* fd) {
|
||||||
if (fd->name() == _field_name) {
|
if (fd->name() == _field_name) {
|
||||||
assert(!_found, "fields cannot be overloaded");
|
assert(!_found, "fields can never be overloaded");
|
||||||
assert(is_reference_type(fd->field_type()), "can archive only fields that are references");
|
if (is_reference_type(fd->field_type())) {
|
||||||
_found = true;
|
_found = true;
|
||||||
_offset = fd->offset();
|
_offset = fd->offset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool found() { return _found; }
|
bool found() { return _found; }
|
||||||
|
@ -1411,21 +1477,79 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
|
void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
|
||||||
int num, TRAPS) {
|
TRAPS) {
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; fields[i].valid(); i++) {
|
||||||
ArchivableStaticFieldInfo* info = &fields[i];
|
ArchivableStaticFieldInfo* info = &fields[i];
|
||||||
TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name);
|
TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name);
|
||||||
TempNewSymbol field_name = SymbolTable::new_symbol(info->field_name);
|
TempNewSymbol field_name = SymbolTable::new_symbol(info->field_name);
|
||||||
|
ResourceMark rm; // for stringStream::as_string() etc.
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
bool is_test_class = (ArchiveHeapTestClass != NULL) && (strcmp(info->klass_name, ArchiveHeapTestClass) == 0);
|
||||||
|
#else
|
||||||
|
bool is_test_class = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (is_test_class) {
|
||||||
|
log_warning(cds)("Loading ArchiveHeapTestClass %s ...", ArchiveHeapTestClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
Klass* k = SystemDictionary::resolve_or_fail(klass_name, true, THREAD);
|
||||||
|
if (HAS_PENDING_EXCEPTION) {
|
||||||
|
CLEAR_PENDING_EXCEPTION;
|
||||||
|
stringStream st;
|
||||||
|
st.print("Fail to initialize archive heap: %s cannot be loaded by the boot loader", info->klass_name);
|
||||||
|
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), st.as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!k->is_instance_klass()) {
|
||||||
|
stringStream st;
|
||||||
|
st.print("Fail to initialize archive heap: %s is not an instance class", info->klass_name);
|
||||||
|
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), st.as_string());
|
||||||
|
}
|
||||||
|
|
||||||
Klass* k = SystemDictionary::resolve_or_fail(klass_name, true, CHECK);
|
|
||||||
InstanceKlass* ik = InstanceKlass::cast(k);
|
InstanceKlass* ik = InstanceKlass::cast(k);
|
||||||
assert(InstanceKlass::cast(ik)->is_shared_boot_class(),
|
assert(InstanceKlass::cast(ik)->is_shared_boot_class(),
|
||||||
"Only support boot classes");
|
"Only support boot classes");
|
||||||
|
|
||||||
|
if (is_test_class) {
|
||||||
|
if (ik->module()->is_named()) {
|
||||||
|
// We don't want ArchiveHeapTestClass to be abused to easily load/initialize arbitrary
|
||||||
|
// core-lib classes. You need to at least append to the bootclasspath.
|
||||||
|
stringStream st;
|
||||||
|
st.print("ArchiveHeapTestClass %s is not in unnamed module", ArchiveHeapTestClass);
|
||||||
|
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), st.as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ik->package() != NULL) {
|
||||||
|
// This restriction makes HeapShared::is_a_test_class_in_unnamed_module() easy.
|
||||||
|
stringStream st;
|
||||||
|
st.print("ArchiveHeapTestClass %s is not in unnamed package", ArchiveHeapTestClass);
|
||||||
|
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), st.as_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ik->module()->name() != vmSymbols::java_base()) {
|
||||||
|
// We don't want to deal with cases when a module is unavailable at runtime.
|
||||||
|
// FUTURE -- load from archived heap only when module graph has not changed
|
||||||
|
// between dump and runtime.
|
||||||
|
stringStream st;
|
||||||
|
st.print("%s is not in java.base module", info->klass_name);
|
||||||
|
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), st.as_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_test_class) {
|
||||||
|
log_warning(cds)("Initializing ArchiveHeapTestClass %s ...", ArchiveHeapTestClass);
|
||||||
|
}
|
||||||
ik->initialize(CHECK);
|
ik->initialize(CHECK);
|
||||||
|
|
||||||
ArchivableStaticFieldFinder finder(ik, field_name);
|
ArchivableStaticFieldFinder finder(ik, field_name);
|
||||||
ik->do_local_static_fields(&finder);
|
ik->do_local_static_fields(&finder);
|
||||||
assert(finder.found(), "field must exist");
|
if (!finder.found()) {
|
||||||
|
stringStream st;
|
||||||
|
st.print("Unable to find the static T_OBJECT field %s::%s", info->klass_name, info->field_name);
|
||||||
|
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), st.as_string());
|
||||||
|
}
|
||||||
|
|
||||||
info->klass = ik;
|
info->klass = ik;
|
||||||
info->offset = finder.offset();
|
info->offset = finder.offset();
|
||||||
|
@ -1435,28 +1559,84 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
|
||||||
void HeapShared::init_subgraph_entry_fields(TRAPS) {
|
void HeapShared::init_subgraph_entry_fields(TRAPS) {
|
||||||
assert(HeapShared::can_write(), "must be");
|
assert(HeapShared::can_write(), "must be");
|
||||||
_dump_time_subgraph_info_table = new (ResourceObj::C_HEAP, mtClass)DumpTimeKlassSubGraphInfoTable();
|
_dump_time_subgraph_info_table = new (ResourceObj::C_HEAP, mtClass)DumpTimeKlassSubGraphInfoTable();
|
||||||
init_subgraph_entry_fields(closed_archive_subgraph_entry_fields,
|
init_subgraph_entry_fields(closed_archive_subgraph_entry_fields, CHECK);
|
||||||
num_closed_archive_subgraph_entry_fields,
|
init_subgraph_entry_fields(open_archive_subgraph_entry_fields, CHECK);
|
||||||
CHECK);
|
|
||||||
init_subgraph_entry_fields(open_archive_subgraph_entry_fields,
|
|
||||||
num_open_archive_subgraph_entry_fields,
|
|
||||||
CHECK);
|
|
||||||
if (MetaspaceShared::use_full_module_graph()) {
|
if (MetaspaceShared::use_full_module_graph()) {
|
||||||
init_subgraph_entry_fields(fmg_open_archive_subgraph_entry_fields,
|
init_subgraph_entry_fields(fmg_open_archive_subgraph_entry_fields, CHECK);
|
||||||
num_fmg_open_archive_subgraph_entry_fields,
|
|
||||||
CHECK);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef PRODUCT
|
||||||
|
void HeapShared::setup_test_class(const char* test_class_name) {
|
||||||
|
ArchivableStaticFieldInfo* p = open_archive_subgraph_entry_fields;
|
||||||
|
int num_slots = sizeof(open_archive_subgraph_entry_fields) / sizeof(ArchivableStaticFieldInfo);
|
||||||
|
assert(p[num_slots - 2].klass_name == NULL, "must have empty slot that's patched below");
|
||||||
|
assert(p[num_slots - 1].klass_name == NULL, "must have empty slot that marks the end of the list");
|
||||||
|
|
||||||
|
if (test_class_name != NULL) {
|
||||||
|
p[num_slots - 2].klass_name = test_class_name;
|
||||||
|
p[num_slots - 2].field_name = ARCHIVE_TEST_FIELD_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if ik is one of the test classes that are pulled in by -XX:ArchiveHeapTestClass
|
||||||
|
// during runtime. This may be called before the module system is initialized so
|
||||||
|
// we cannot rely on InstanceKlass::module(), etc.
|
||||||
|
bool HeapShared::is_a_test_class_in_unnamed_module(Klass* ik) {
|
||||||
|
if (_test_class != NULL) {
|
||||||
|
if (ik == _test_class) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Array<Klass*>* klasses = _test_class_record->subgraph_object_klasses();
|
||||||
|
if (klasses == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < klasses->length(); i++) {
|
||||||
|
Klass* k = klasses->at(i);
|
||||||
|
if (k == ik) {
|
||||||
|
Symbol* name;
|
||||||
|
if (k->is_instance_klass()) {
|
||||||
|
name = InstanceKlass::cast(k)->name();
|
||||||
|
} else if (k->is_objArray_klass()) {
|
||||||
|
Klass* bk = ObjArrayKlass::cast(k)->bottom_klass();
|
||||||
|
if (!bk->is_instance_klass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
name = bk->name();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See KlassSubGraphInfo::check_allowed_klass() - only two types of
|
||||||
|
// classes are allowed:
|
||||||
|
// (A) java.base classes (which must not be in the unnamed module)
|
||||||
|
// (B) test classes which must be in the unnamed package of the unnamed module.
|
||||||
|
// So if we see a '/' character in the class name, it must be in (A);
|
||||||
|
// otherwise it must be in (B).
|
||||||
|
if (name->index_of_at(0, "/", 1) >= 0) {
|
||||||
|
return false; // (A)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // (B)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void HeapShared::init_for_dumping(TRAPS) {
|
void HeapShared::init_for_dumping(TRAPS) {
|
||||||
if (HeapShared::can_write()) {
|
if (HeapShared::can_write()) {
|
||||||
|
setup_test_class(ArchiveHeapTestClass);
|
||||||
_dumped_interned_strings = new (ResourceObj::C_HEAP, mtClass)DumpedInternedStrings();
|
_dumped_interned_strings = new (ResourceObj::C_HEAP, mtClass)DumpedInternedStrings();
|
||||||
init_subgraph_entry_fields(CHECK);
|
init_subgraph_entry_fields(CHECK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
int num, bool is_closed_archive,
|
bool is_closed_archive,
|
||||||
bool is_full_module_graph) {
|
bool is_full_module_graph) {
|
||||||
_num_total_subgraph_recordings = 0;
|
_num_total_subgraph_recordings = 0;
|
||||||
_num_total_walked_objs = 0;
|
_num_total_walked_objs = 0;
|
||||||
|
@ -1471,7 +1651,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
// At runtime, these classes are initialized before X's archived fields
|
// At runtime, these classes are initialized before X's archived fields
|
||||||
// are restored by HeapShared::initialize_from_archived_subgraph().
|
// are restored by HeapShared::initialize_from_archived_subgraph().
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < num; ) {
|
for (int i = 0; fields[i].valid(); ) {
|
||||||
ArchivableStaticFieldInfo* info = &fields[i];
|
ArchivableStaticFieldInfo* info = &fields[i];
|
||||||
const char* klass_name = info->klass_name;
|
const char* klass_name = info->klass_name;
|
||||||
start_recording_subgraph(info->klass, klass_name, is_full_module_graph);
|
start_recording_subgraph(info->klass, klass_name, is_full_module_graph);
|
||||||
|
@ -1480,7 +1660,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
// fields[], these will be archived in the same
|
// fields[], these will be archived in the same
|
||||||
// {start_recording_subgraph ... done_recording_subgraph} pass to
|
// {start_recording_subgraph ... done_recording_subgraph} pass to
|
||||||
// save time.
|
// save time.
|
||||||
for (; i < num; i++) {
|
for (; fields[i].valid(); i++) {
|
||||||
ArchivableStaticFieldInfo* f = &fields[i];
|
ArchivableStaticFieldInfo* f = &fields[i];
|
||||||
if (f->klass_name != klass_name) {
|
if (f->klass_name != klass_name) {
|
||||||
break;
|
break;
|
||||||
|
@ -1501,7 +1681,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
log_info(cds, heap)(" Recorded %d klasses", _num_total_recorded_klasses);
|
log_info(cds, heap)(" Recorded %d klasses", _num_total_recorded_klasses);
|
||||||
|
|
||||||
#ifndef PRODUCT
|
#ifndef PRODUCT
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; fields[i].valid(); i++) {
|
||||||
ArchivableStaticFieldInfo* f = &fields[i];
|
ArchivableStaticFieldInfo* f = &fields[i];
|
||||||
verify_subgraph_from_static_field(f->klass, f->offset);
|
verify_subgraph_from_static_field(f->klass, f->offset);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,13 +45,7 @@ class FileMapInfo;
|
||||||
class KlassSubGraphInfo;
|
class KlassSubGraphInfo;
|
||||||
class ResourceBitMap;
|
class ResourceBitMap;
|
||||||
|
|
||||||
struct ArchivableStaticFieldInfo {
|
struct ArchivableStaticFieldInfo;
|
||||||
const char* klass_name;
|
|
||||||
const char* field_name;
|
|
||||||
InstanceKlass* klass;
|
|
||||||
int offset;
|
|
||||||
BasicType type;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A dump time sub-graph info for Klass _k. It includes the entry points
|
// A dump time sub-graph info for Klass _k. It includes the entry points
|
||||||
// (static fields in _k's mirror) of the archived sub-graphs reachable
|
// (static fields in _k's mirror) of the archived sub-graphs reachable
|
||||||
|
@ -78,7 +72,7 @@ class KlassSubGraphInfo: public CHeapObj<mtClass> {
|
||||||
// used at runtime if JVMTI ClassFileLoadHook is enabled.
|
// used at runtime if JVMTI ClassFileLoadHook is enabled.
|
||||||
bool _has_non_early_klasses;
|
bool _has_non_early_klasses;
|
||||||
static bool is_non_early_klass(Klass* k);
|
static bool is_non_early_klass(Klass* k);
|
||||||
|
static void check_allowed_klass(InstanceKlass* ik);
|
||||||
public:
|
public:
|
||||||
KlassSubGraphInfo(Klass* k, bool is_full_module_graph) :
|
KlassSubGraphInfo(Klass* k, bool is_full_module_graph) :
|
||||||
_k(k), _subgraph_object_klasses(NULL),
|
_k(k), _subgraph_object_klasses(NULL),
|
||||||
|
@ -229,7 +223,6 @@ private:
|
||||||
static void check_closed_region_object(InstanceKlass* k);
|
static void check_closed_region_object(InstanceKlass* k);
|
||||||
static CachedOopInfo make_cached_oop_info(oop orig_obj);
|
static CachedOopInfo make_cached_oop_info(oop orig_obj);
|
||||||
static void archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
static void archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
int num,
|
|
||||||
bool is_closed_archive,
|
bool is_closed_archive,
|
||||||
bool is_full_module_graph);
|
bool is_full_module_graph);
|
||||||
|
|
||||||
|
@ -249,8 +242,7 @@ private:
|
||||||
static KlassSubGraphInfo* get_subgraph_info(Klass *k);
|
static KlassSubGraphInfo* get_subgraph_info(Klass *k);
|
||||||
|
|
||||||
static void init_subgraph_entry_fields(TRAPS) NOT_CDS_JAVA_HEAP_RETURN;
|
static void init_subgraph_entry_fields(TRAPS) NOT_CDS_JAVA_HEAP_RETURN;
|
||||||
static void init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
|
static void init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], TRAPS);
|
||||||
int num, TRAPS);
|
|
||||||
|
|
||||||
// UseCompressedOops only: Used by decode_from_archive
|
// UseCompressedOops only: Used by decode_from_archive
|
||||||
static address _narrow_oop_base;
|
static address _narrow_oop_base;
|
||||||
|
@ -303,7 +295,7 @@ private:
|
||||||
static void copy_roots();
|
static void copy_roots();
|
||||||
|
|
||||||
static void resolve_classes_for_subgraphs(ArchivableStaticFieldInfo fields[],
|
static void resolve_classes_for_subgraphs(ArchivableStaticFieldInfo fields[],
|
||||||
int num, JavaThread* THREAD);
|
JavaThread* THREAD);
|
||||||
static void resolve_classes_for_subgraph_of(Klass* k, JavaThread* THREAD);
|
static void resolve_classes_for_subgraph_of(Klass* k, JavaThread* THREAD);
|
||||||
static void clear_archived_roots_of(Klass* k);
|
static void clear_archived_roots_of(Klass* k);
|
||||||
static const ArchivedKlassSubGraphInfoRecord*
|
static const ArchivedKlassSubGraphInfoRecord*
|
||||||
|
@ -396,6 +388,7 @@ private:
|
||||||
// Run-time only
|
// Run-time only
|
||||||
static void clear_root(int index);
|
static void clear_root(int index);
|
||||||
|
|
||||||
|
static void setup_test_class(const char* test_class_name) PRODUCT_RETURN;
|
||||||
#endif // INCLUDE_CDS_JAVA_HEAP
|
#endif // INCLUDE_CDS_JAVA_HEAP
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -423,6 +416,7 @@ private:
|
||||||
static oop to_requested_address(oop dumptime_oop) {
|
static oop to_requested_address(oop dumptime_oop) {
|
||||||
return cast_to_oop(to_requested_address(cast_from_oop<address>(dumptime_oop)));
|
return cast_to_oop(to_requested_address(cast_from_oop<address>(dumptime_oop)));
|
||||||
}
|
}
|
||||||
|
static bool is_a_test_class_in_unnamed_module(Klass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
#if INCLUDE_CDS_JAVA_HEAP
|
#if INCLUDE_CDS_JAVA_HEAP
|
||||||
|
|
|
@ -637,7 +637,7 @@ void ModuleEntryTable::finalize_javabase(Handle module_handle, Symbol* version,
|
||||||
// be set with the defining module. During startup, prior to java.base's definition,
|
// be set with the defining module. During startup, prior to java.base's definition,
|
||||||
// classes needing their module field set are added to the fixup_module_list.
|
// classes needing their module field set are added to the fixup_module_list.
|
||||||
// Their module field is set once java.base's java.lang.Module is known to the VM.
|
// Their module field is set once java.base's java.lang.Module is known to the VM.
|
||||||
void ModuleEntryTable::patch_javabase_entries(Handle module_handle) {
|
void ModuleEntryTable::patch_javabase_entries(JavaThread* current, Handle module_handle) {
|
||||||
if (module_handle.is_null()) {
|
if (module_handle.is_null()) {
|
||||||
fatal("Unable to patch the module field of classes loaded prior to "
|
fatal("Unable to patch the module field of classes loaded prior to "
|
||||||
JAVA_BASE_NAME "'s definition, invalid java.lang.Module");
|
JAVA_BASE_NAME "'s definition, invalid java.lang.Module");
|
||||||
|
@ -660,7 +660,18 @@ void ModuleEntryTable::patch_javabase_entries(Handle module_handle) {
|
||||||
for (int i = 0; i < list_length; i++) {
|
for (int i = 0; i < list_length; i++) {
|
||||||
Klass* k = list->at(i);
|
Klass* k = list->at(i);
|
||||||
assert(k->is_klass(), "List should only hold classes");
|
assert(k->is_klass(), "List should only hold classes");
|
||||||
java_lang_Class::fixup_module_field(k, module_handle);
|
#ifndef PRODUCT
|
||||||
|
if (HeapShared::is_a_test_class_in_unnamed_module(k)) {
|
||||||
|
// We allow -XX:ArchiveHeapTestClass to archive additional classes
|
||||||
|
// into the CDS heap, but these must be in the unnamed module.
|
||||||
|
ModuleEntry* unnamed_module = ClassLoaderData::the_null_class_loader_data()->unnamed_module();
|
||||||
|
Handle unnamed_module_handle(current, unnamed_module->module());
|
||||||
|
java_lang_Class::fixup_module_field(k, unnamed_module_handle);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
java_lang_Class::fixup_module_field(k, module_handle);
|
||||||
|
}
|
||||||
k->class_loader_data()->dec_keep_alive();
|
k->class_loader_data()->dec_keep_alive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,7 +238,7 @@ public:
|
||||||
static bool javabase_defined() { return ((_javabase_module != NULL) &&
|
static bool javabase_defined() { return ((_javabase_module != NULL) &&
|
||||||
(_javabase_module->module() != NULL)); }
|
(_javabase_module->module() != NULL)); }
|
||||||
static void finalize_javabase(Handle module_handle, Symbol* version, Symbol* location);
|
static void finalize_javabase(Handle module_handle, Symbol* version, Symbol* location);
|
||||||
static void patch_javabase_entries(Handle module_handle);
|
static void patch_javabase_entries(JavaThread* current, Handle module_handle);
|
||||||
|
|
||||||
void modules_do(void f(ModuleEntry*));
|
void modules_do(void f(ModuleEntry*));
|
||||||
void modules_do(ModuleClosure* closure);
|
void modules_do(ModuleClosure* closure);
|
||||||
|
|
|
@ -237,7 +237,7 @@ static void define_javabase_module(Handle module_handle, jstring version, jstrin
|
||||||
// so no locking is needed.
|
// so no locking is needed.
|
||||||
|
|
||||||
// Patch any previously loaded class's module field with java.base's java.lang.Module.
|
// Patch any previously loaded class's module field with java.base's java.lang.Module.
|
||||||
ModuleEntryTable::patch_javabase_entries(module_handle);
|
ModuleEntryTable::patch_javabase_entries(THREAD, module_handle);
|
||||||
|
|
||||||
log_info(module, load)(JAVA_BASE_NAME " location: %s",
|
log_info(module, load)(JAVA_BASE_NAME " location: %s",
|
||||||
location_symbol != NULL ? location_symbol->as_C_string() : "NULL");
|
location_symbol != NULL ? location_symbol->as_C_string() : "NULL");
|
||||||
|
@ -489,7 +489,7 @@ void Modules::define_archived_modules(Handle h_platform_loader, Handle h_system_
|
||||||
|
|
||||||
Handle java_base_module(THREAD, ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data());
|
Handle java_base_module(THREAD, ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data());
|
||||||
// Patch any previously loaded class's module field with java.base's java.lang.Module.
|
// Patch any previously loaded class's module field with java.base's java.lang.Module.
|
||||||
ModuleEntryTable::patch_javabase_entries(java_base_module);
|
ModuleEntryTable::patch_javabase_entries(THREAD, java_base_module);
|
||||||
|
|
||||||
if (h_platform_loader.is_null()) {
|
if (h_platform_loader.is_null()) {
|
||||||
THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object");
|
THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object");
|
||||||
|
|
|
@ -1042,8 +1042,13 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name,
|
||||||
assert(scp_index >= 0, "must be");
|
assert(scp_index >= 0, "must be");
|
||||||
SharedClassPathEntry* scp_entry = FileMapInfo::shared_path(scp_index);
|
SharedClassPathEntry* scp_entry = FileMapInfo::shared_path(scp_index);
|
||||||
if (!Universe::is_module_initialized()) {
|
if (!Universe::is_module_initialized()) {
|
||||||
assert(scp_entry != NULL && scp_entry->is_modules_image(),
|
assert(scp_entry != NULL, "must be");
|
||||||
"Loading non-bootstrap classes before the module system is initialized");
|
// At this point, no modules have been defined yet. KlassSubGraphInfo::check_allowed_klass()
|
||||||
|
// has restricted the classes can be loaded at this step to be only:
|
||||||
|
// [1] scp_entry->is_modules_image(): classes in java.base, or,
|
||||||
|
// [2] HeapShared::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module
|
||||||
|
assert(scp_entry->is_modules_image() || HeapShared::is_a_test_class_in_unnamed_module(ik),
|
||||||
|
"only these classes can be loaded before the module system is initialized");
|
||||||
assert(class_loader.is_null(), "sanity");
|
assert(class_loader.is_null(), "sanity");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* 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
|
||||||
|
@ -117,14 +117,15 @@ void Symbol::set_permanent() {
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Symbol::index_of
|
// Symbol::index_of
|
||||||
//
|
//
|
||||||
// Finds if the given string is a substring of this symbol's utf8 bytes.
|
// Test if we have the give substring at or after the i-th char of this
|
||||||
// Return -1 on failure. Otherwise return the first index where str occurs.
|
// symbol's utf8 bytes.
|
||||||
int Symbol::index_of_at(int i, const char* str, int len) const {
|
// Return -1 on failure. Otherwise return the first index where substr occurs.
|
||||||
|
int Symbol::index_of_at(int i, const char* substr, int substr_len) const {
|
||||||
assert(i >= 0 && i <= utf8_length(), "oob");
|
assert(i >= 0 && i <= utf8_length(), "oob");
|
||||||
if (len <= 0) return 0;
|
if (substr_len <= 0) return 0;
|
||||||
char first_char = str[0];
|
char first_char = substr[0];
|
||||||
address bytes = (address) ((Symbol*)this)->base();
|
address bytes = (address) ((Symbol*)this)->base();
|
||||||
address limit = bytes + utf8_length() - len; // inclusive limit
|
address limit = bytes + utf8_length() - substr_len; // inclusive limit
|
||||||
address scan = bytes + i;
|
address scan = bytes + i;
|
||||||
if (scan > limit)
|
if (scan > limit)
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -133,9 +134,9 @@ int Symbol::index_of_at(int i, const char* str, int len) const {
|
||||||
if (scan == NULL)
|
if (scan == NULL)
|
||||||
return -1; // not found
|
return -1; // not found
|
||||||
assert(scan >= bytes+i && scan <= limit, "scan oob");
|
assert(scan >= bytes+i && scan <= limit, "scan oob");
|
||||||
if (len <= 2
|
if (substr_len <= 2
|
||||||
? (char) scan[len-1] == str[len-1]
|
? (char) scan[substr_len-1] == substr[substr_len-1]
|
||||||
: memcmp(scan+1, str+1, len-1) == 0) {
|
: memcmp(scan+1, substr+1, substr_len-1) == 0) {
|
||||||
return (int)(scan - bytes);
|
return (int)(scan - bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,8 +240,8 @@ class Symbol : public MetaspaceObj {
|
||||||
return code_byte == char_at(position);
|
return code_byte == char_at(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests if the symbol starts with the given prefix.
|
// Test if the symbol has the give substring at or after the i-th char.
|
||||||
int index_of_at(int i, const char* str, int len) const;
|
int index_of_at(int i, const char* substr, int substr_len) const;
|
||||||
|
|
||||||
// Three-way compare for sorting; returns -1/0/1 if receiver is </==/> than arg
|
// Three-way compare for sorting; returns -1/0/1 if receiver is </==/> than arg
|
||||||
// note that the ordering is not alfabetical
|
// note that the ordering is not alfabetical
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8214781 8293187
|
||||||
|
* @summary Test for the -XX:ArchiveHeapTestClass flag
|
||||||
|
* @requires vm.cds.write.archived.java.heap
|
||||||
|
* @modules java.base/sun.invoke.util java.logging
|
||||||
|
* @library /test/jdk/lib/testlibrary /test/lib
|
||||||
|
* /test/hotspot/jtreg/runtime/cds/appcds
|
||||||
|
* /test/hotspot/jtreg/runtime/cds/appcds/test-classes
|
||||||
|
* @build ArchiveHeapTestClass Hello pkg.ClassInPackage
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar
|
||||||
|
* CDSTestClassA CDSTestClassA$XX CDSTestClassA$YY
|
||||||
|
* CDSTestClassB CDSTestClassC CDSTestClassD
|
||||||
|
* CDSTestClassE CDSTestClassF CDSTestClassG
|
||||||
|
* pkg.ClassInPackage
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello
|
||||||
|
* @run driver ArchiveHeapTestClass
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jdk.test.lib.Platform;
|
||||||
|
import jdk.test.lib.helpers.ClassFileInstaller;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
|
||||||
|
public class ArchiveHeapTestClass {
|
||||||
|
static final String bootJar = ClassFileInstaller.getJarPath("boot.jar");
|
||||||
|
static final String appJar = ClassFileInstaller.getJarPath("app.jar");
|
||||||
|
static final String[] appClassList = {"Hello"};
|
||||||
|
|
||||||
|
static final String CDSTestClassA_name = CDSTestClassA.class.getName();
|
||||||
|
static final String CDSTestClassB_name = CDSTestClassB.class.getName();
|
||||||
|
static final String CDSTestClassC_name = CDSTestClassC.class.getName();
|
||||||
|
static final String CDSTestClassD_name = CDSTestClassD.class.getName();
|
||||||
|
static final String CDSTestClassE_name = CDSTestClassE.class.getName();
|
||||||
|
static final String CDSTestClassF_name = CDSTestClassF.class.getName();
|
||||||
|
static final String CDSTestClassG_name = CDSTestClassG.class.getName();
|
||||||
|
static final String ClassInPackage_name = pkg.ClassInPackage.class.getName().replace('.', '/');
|
||||||
|
static final String ARCHIVE_TEST_FIELD_NAME = "archivedObjects";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (Platform.isDebugBuild()) {
|
||||||
|
testDebugBuild();
|
||||||
|
} else {
|
||||||
|
testProductBuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static OutputAnalyzer dumpHelloOnly(String... extraOpts) throws Exception {
|
||||||
|
return TestCommon.dump(appJar, appClassList, extraOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static OutputAnalyzer dumpBootAndHello(String bootClass, String... extraOpts) throws Exception {
|
||||||
|
String classlist[] = TestCommon.concat(appClassList, bootClass);
|
||||||
|
extraOpts = TestCommon.concat(extraOpts,
|
||||||
|
"-Xbootclasspath/a:" + bootJar,
|
||||||
|
"-XX:ArchiveHeapTestClass=" + bootClass,
|
||||||
|
"-Xlog:cds+heap");
|
||||||
|
return TestCommon.dump(appJar, classlist, extraOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int caseNum = 0;
|
||||||
|
static void testCase(String s) {
|
||||||
|
System.out.println("==================================================");
|
||||||
|
System.out.println(" Test " + (++caseNum) + ": " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mustContain(OutputAnalyzer output, String... expectStrs) throws Exception {
|
||||||
|
for (String s : expectStrs) {
|
||||||
|
output.shouldContain(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mustFail(OutputAnalyzer output, String... expectStrs) throws Exception {
|
||||||
|
mustContain(output, expectStrs);
|
||||||
|
output.shouldNotHaveExitValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mustSucceed(OutputAnalyzer output, String... expectStrs) throws Exception {
|
||||||
|
mustContain(output, expectStrs);
|
||||||
|
output.shouldHaveExitValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testDebugBuild() throws Exception {
|
||||||
|
OutputAnalyzer output;
|
||||||
|
|
||||||
|
testCase("Simple positive case");
|
||||||
|
output = dumpBootAndHello(CDSTestClassA_name);
|
||||||
|
mustSucceed(output, CDSTestClassA.getOutput()); // make sure <clinit> is executed
|
||||||
|
output.shouldMatch("warning.*cds.*Loading ArchiveHeapTestClass " + CDSTestClassA_name);
|
||||||
|
output.shouldMatch("warning.*cds.*Initializing ArchiveHeapTestClass " + CDSTestClassA_name);
|
||||||
|
output.shouldContain("Archived field " + CDSTestClassA_name + "::" + ARCHIVE_TEST_FIELD_NAME);
|
||||||
|
output.shouldMatch("Archived object klass CDSTestClassA .*\\[LCDSTestClassA;");
|
||||||
|
output.shouldMatch("Archived object klass CDSTestClassA .*CDSTestClassA\\$YY");
|
||||||
|
|
||||||
|
TestCommon.run("-Xbootclasspath/a:" + bootJar, "-cp", appJar, "-Xlog:cds+heap", CDSTestClassA_name)
|
||||||
|
.assertNormalExit(CDSTestClassA.getOutput(),
|
||||||
|
"resolve subgraph " + CDSTestClassA_name);
|
||||||
|
|
||||||
|
testCase("Class doesn't exist");
|
||||||
|
output = dumpHelloOnly("-XX:ArchiveHeapTestClass=NoSuchClass");
|
||||||
|
mustFail(output, "Fail to initialize archive heap: NoSuchClass cannot be loaded");
|
||||||
|
|
||||||
|
testCase("Class doesn't exist (objarray)");
|
||||||
|
output = dumpHelloOnly("-XX:ArchiveHeapTestClass=[LNoSuchClass;");
|
||||||
|
mustFail(output, "Fail to initialize archive heap: [LNoSuchClass; cannot be loaded");
|
||||||
|
|
||||||
|
testCase("Not an instance klass");
|
||||||
|
output = dumpHelloOnly("-XX:ArchiveHeapTestClass=[Ljava/lang/Object;");
|
||||||
|
mustFail(output, "Fail to initialize archive heap: [Ljava/lang/Object; is not an instance class");
|
||||||
|
|
||||||
|
testCase("Not in boot loader");
|
||||||
|
output = dumpHelloOnly("-XX:ArchiveHeapTestClass=Hello");
|
||||||
|
mustFail(output, "Fail to initialize archive heap: Hello cannot be loaded by the boot loader");
|
||||||
|
|
||||||
|
testCase("Not from unnamed module");
|
||||||
|
output = dumpHelloOnly("-XX:ArchiveHeapTestClass=java/lang/Object");
|
||||||
|
mustFail(output, "ArchiveHeapTestClass java/lang/Object is not in unnamed module");
|
||||||
|
|
||||||
|
testCase("Not from unnamed package");
|
||||||
|
output = dumpBootAndHello(ClassInPackage_name);
|
||||||
|
mustFail(output, "ArchiveHeapTestClass pkg/ClassInPackage is not in unnamed package");
|
||||||
|
|
||||||
|
testCase("Field not found");
|
||||||
|
output = dumpBootAndHello(CDSTestClassB_name);
|
||||||
|
mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassB::archivedObjects");
|
||||||
|
|
||||||
|
testCase("Not a static field");
|
||||||
|
output = dumpBootAndHello(CDSTestClassC_name);
|
||||||
|
mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassC::archivedObjects");
|
||||||
|
|
||||||
|
testCase("Not a T_OBJECT field");
|
||||||
|
output = dumpBootAndHello(CDSTestClassD_name);
|
||||||
|
mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassD::archivedObjects");
|
||||||
|
|
||||||
|
testCase("Use a disallowed class: in unnamed module but not in unname package");
|
||||||
|
output = dumpBootAndHello(CDSTestClassE_name);
|
||||||
|
mustFail(output, "Class pkg.ClassInPackage not allowed in archive heap");
|
||||||
|
|
||||||
|
testCase("Use a disallowed class: not in java.base module");
|
||||||
|
output = dumpBootAndHello(CDSTestClassF_name);
|
||||||
|
mustFail(output, "Class java.util.logging.Level not allowed in archive heap");
|
||||||
|
|
||||||
|
if (false) { // JDK-8293187
|
||||||
|
testCase("sun.invoke.util.Wrapper");
|
||||||
|
output = dumpBootAndHello(CDSTestClassG_name);
|
||||||
|
mustSucceed(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testProductBuild() throws Exception {
|
||||||
|
OutputAnalyzer output;
|
||||||
|
|
||||||
|
output = dumpHelloOnly("-XX:ArchiveHeapTestClass=NoSuchClass");
|
||||||
|
mustFail(output, "VM option 'ArchiveHeapTestClass' is develop and is available only in debug version of VM.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassA {
|
||||||
|
static final String output = "CDSTestClassA.<clinit> was executed";
|
||||||
|
static Object[] archivedObjects;
|
||||||
|
static {
|
||||||
|
archivedObjects = new Object[5];
|
||||||
|
archivedObjects[0] = output;
|
||||||
|
archivedObjects[1] = new CDSTestClassA[0];
|
||||||
|
archivedObjects[2] = new YY();
|
||||||
|
archivedObjects[3] = new int[0];
|
||||||
|
archivedObjects[4] = new int[2][2];
|
||||||
|
System.out.println(output);
|
||||||
|
System.out.println("CDSTestClassA module = " + CDSTestClassA.class.getModule());
|
||||||
|
System.out.println("CDSTestClassA package = " + CDSTestClassA.class.getPackage());
|
||||||
|
System.out.println("CDSTestClassA[] module = " + archivedObjects[1].getClass().getModule());
|
||||||
|
System.out.println("CDSTestClassA[] package = " + archivedObjects[1].getClass().getPackage());
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getOutput() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
if (CDSTestClassA.class.getModule().isNamed()) {
|
||||||
|
throw new RuntimeException("CDSTestClassA must be in unnamed module");
|
||||||
|
}
|
||||||
|
if (CDSTestClassA.class.getPackage() != null) {
|
||||||
|
throw new RuntimeException("CDSTestClassA must be in null package");
|
||||||
|
}
|
||||||
|
if (archivedObjects[1].getClass().getModule().isNamed()) {
|
||||||
|
throw new RuntimeException("CDSTestClassA[] must be in unnamed module");
|
||||||
|
}
|
||||||
|
if (archivedObjects[1].getClass().getPackage() != null) {
|
||||||
|
throw new RuntimeException("CDSTestClassA[] must be in null package");
|
||||||
|
}
|
||||||
|
XX.doit();
|
||||||
|
YY.doit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an inner class that has NOT been archived.
|
||||||
|
static class XX {
|
||||||
|
static void doit() {
|
||||||
|
System.out.println("XX module = " + XX.class.getModule());
|
||||||
|
System.out.println("XX package = " + XX.class.getPackage());
|
||||||
|
|
||||||
|
if (XX.class.getModule().isNamed()) {
|
||||||
|
throw new RuntimeException("XX must be in unnamed module");
|
||||||
|
}
|
||||||
|
if (XX.class.getPackage() != null) {
|
||||||
|
throw new RuntimeException("XX must be in null package");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an inner class that HAS been archived.
|
||||||
|
static class YY {
|
||||||
|
static void doit() {
|
||||||
|
System.out.println("YY module = " + YY.class.getModule());
|
||||||
|
System.out.println("YY package = " + YY.class.getPackage());
|
||||||
|
|
||||||
|
if (YY.class.getModule().isNamed()) {
|
||||||
|
throw new RuntimeException("YY must be in unnamed module");
|
||||||
|
}
|
||||||
|
if (YY.class.getPackage() != null) {
|
||||||
|
throw new RuntimeException("YY must be in null package");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassB {
|
||||||
|
// No field named "archivedObjects"
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassC {
|
||||||
|
Object[] archivedObjects; // Not a static field
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassD {
|
||||||
|
static int archivedObjects; // Not an int field
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassE {
|
||||||
|
static Object[] archivedObjects;
|
||||||
|
static {
|
||||||
|
// Not in unnamed package of unnamed module
|
||||||
|
archivedObjects = new Object[1];
|
||||||
|
archivedObjects[0] = new pkg.ClassInPackage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassF {
|
||||||
|
static Object[] archivedObjects;
|
||||||
|
static {
|
||||||
|
// Not in java.base
|
||||||
|
archivedObjects = new Object[1];
|
||||||
|
archivedObjects[0] = java.util.logging.Level.OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDSTestClassG {
|
||||||
|
static Object[] archivedObjects;
|
||||||
|
static {
|
||||||
|
// Not in java.base
|
||||||
|
archivedObjects = new Object[1];
|
||||||
|
archivedObjects[0] = sun.invoke.util.Wrapper.BOOLEAN;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pkg;
|
||||||
|
|
||||||
|
public class ClassInPackage {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue