8293182: Improve testing of CDS archive heap

Reviewed-by: ccheung, coleenp
This commit is contained in:
Ioi Lam 2022-09-07 23:02:35 +00:00
parent 51de765867
commit f84386cf6e
11 changed files with 594 additions and 81 deletions

View File

@ -71,6 +71,11 @@
product(bool, AllowArchivingWithJavaAgent, false, DIAGNOSTIC, \
"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, \
"Dump the names all loaded classes, that could be stored into " \
"the CDS archive, in the specified file") \

View File

@ -65,9 +65,33 @@
#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;
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!
//
@ -83,6 +107,7 @@ static ArchivableStaticFieldInfo closed_archive_subgraph_entry_fields[] = {
{"java/lang/Character$CharacterCache", "archivedCache"},
{"java/util/jar/Attributes$Name", "KNOWN_NAMES"},
{"sun/util/locale/BaseLocale", "constantBaseLocales"},
{NULL, NULL},
};
// Entry fields for subgraphs archived in the open archive heap region.
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/module/Configuration", "EMPTY_CONFIGURATION"},
{"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).
@ -98,15 +127,9 @@ static ArchivableStaticFieldInfo fmg_open_archive_subgraph_entry_fields[] = {
{"jdk/internal/loader/ArchivedClassLoaders", "archivedClassLoaders"},
{"jdk/internal/module/ArchivedBootLayer", "archivedBootLayer"},
{"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;
OopHandle HeapShared::_roots;
@ -118,8 +141,8 @@ bool HeapShared::is_archived_object_during_dumptime(oop p) {
}
#endif
static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], int num, InstanceKlass* ik) {
for (int i = 0; i < num; i++) {
static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], InstanceKlass* ik) {
for (int i = 0; fields[i].valid(); i++) {
if (fields[i].klass == ik) {
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) {
return is_subgraph_root_class_of(closed_archive_subgraph_entry_fields,
num_closed_archive_subgraph_entry_fields, ik) ||
is_subgraph_root_class_of(open_archive_subgraph_entry_fields,
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);
return is_subgraph_root_class_of(closed_archive_subgraph_entry_fields, ik) ||
is_subgraph_root_class_of(open_archive_subgraph_entry_fields, ik) ||
is_subgraph_root_class_of(fmg_open_archive_subgraph_entry_fields, ik);
}
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);
archive_object_subgraphs(closed_archive_subgraph_entry_fields,
num_closed_archive_subgraph_entry_fields,
true /* is_closed_archive */,
false /* is_full_module_graph */);
@ -495,12 +514,10 @@ void HeapShared::copy_open_objects(GrowableArray<MemRegion>* open_regions) {
archive_klass_objects();
archive_object_subgraphs(open_archive_subgraph_entry_fields,
num_open_archive_subgraph_entry_fields,
false /* is_closed_archive */,
false /* is_full_module_graph */);
if (MetaspaceShared::use_full_module_graph()) {
archive_object_subgraphs(fmg_open_archive_subgraph_entry_fields,
num_fmg_open_archive_subgraph_entry_fields,
false /* is_closed_archive */,
true /* is_full_module_graph */);
ClassLoaderDataShared::init_archived_oops();
@ -612,11 +629,13 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) {
// to the sub-graph object class list.
return;
}
check_allowed_klass(InstanceKlass::cast(orig_k));
} else if (relocated_k->is_objArray_klass()) {
Klass* abk = ObjArrayKlass::cast(relocated_k)->bottom_klass();
if (abk->is_instance_klass()) {
assert(InstanceKlass::cast(abk)->is_shared_boot_class(),
"must be boot class");
check_allowed_klass(InstanceKlass::cast(ObjArrayKlass::cast(orig_k)->bottom_klass()));
}
if (relocated_k == Universe::objectArrayKlassObj()) {
// 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);
}
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) {
if (k->is_objArray_klass()) {
k = ObjArrayKlass::cast(k)->bottom_klass();
@ -753,6 +794,15 @@ void HeapShared::write_subgraph_info_table() {
CopyKlassSubGraphInfoToArchive copy(&writer);
d_table->iterate(&copy);
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) {
@ -772,6 +822,14 @@ void HeapShared::serialize(SerializeClosure* soc) {
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);
}
@ -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
// this case, we will not load the ArchivedKlassSubGraphInfoRecord and will clear its roots.
void HeapShared::resolve_classes(JavaThread* THREAD) {
assert(UseSharedSpaces, "runtime only!");
if (!ArchiveHeapLoader::is_fully_available()) {
return; // nothing to do
}
resolve_classes_for_subgraphs(closed_archive_subgraph_entry_fields,
num_closed_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);
resolve_classes_for_subgraphs(closed_archive_subgraph_entry_fields, THREAD);
resolve_classes_for_subgraphs(open_archive_subgraph_entry_fields, THREAD);
resolve_classes_for_subgraphs(fmg_open_archive_subgraph_entry_fields, THREAD);
}
void HeapShared::resolve_classes_for_subgraphs(ArchivableStaticFieldInfo fields[],
int num, JavaThread* THREAD) {
for (int i = 0; i < num; i++) {
JavaThread* THREAD) {
for (int i = 0; fields[i].valid(); i++) {
ArchivableStaticFieldInfo* info = &fields[i];
TempNewSymbol klass_name = SymbolTable::new_symbol(info->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);
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
// during VM initialization time. No lock is needed.
if (record != NULL) {
@ -899,6 +959,11 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP
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);
// Load/link/initialize the klasses of the objects in the subgraph.
@ -1400,10 +1465,11 @@ public:
virtual void do_field(fieldDescriptor* fd) {
if (fd->name() == _field_name) {
assert(!_found, "fields cannot be overloaded");
assert(is_reference_type(fd->field_type()), "can archive only fields that are references");
_found = true;
_offset = fd->offset();
assert(!_found, "fields can never be overloaded");
if (is_reference_type(fd->field_type())) {
_found = true;
_offset = fd->offset();
}
}
}
bool found() { return _found; }
@ -1411,21 +1477,79 @@ public:
};
void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
int num, TRAPS) {
for (int i = 0; i < num; i++) {
TRAPS) {
for (int i = 0; fields[i].valid(); i++) {
ArchivableStaticFieldInfo* info = &fields[i];
TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_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);
assert(InstanceKlass::cast(ik)->is_shared_boot_class(),
"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);
ArchivableStaticFieldFinder finder(ik, field_name);
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->offset = finder.offset();
@ -1435,28 +1559,84 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
void HeapShared::init_subgraph_entry_fields(TRAPS) {
assert(HeapShared::can_write(), "must be");
_dump_time_subgraph_info_table = new (ResourceObj::C_HEAP, mtClass)DumpTimeKlassSubGraphInfoTable();
init_subgraph_entry_fields(closed_archive_subgraph_entry_fields,
num_closed_archive_subgraph_entry_fields,
CHECK);
init_subgraph_entry_fields(open_archive_subgraph_entry_fields,
num_open_archive_subgraph_entry_fields,
CHECK);
init_subgraph_entry_fields(closed_archive_subgraph_entry_fields, CHECK);
init_subgraph_entry_fields(open_archive_subgraph_entry_fields, CHECK);
if (MetaspaceShared::use_full_module_graph()) {
init_subgraph_entry_fields(fmg_open_archive_subgraph_entry_fields,
num_fmg_open_archive_subgraph_entry_fields,
CHECK);
init_subgraph_entry_fields(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) {
if (HeapShared::can_write()) {
setup_test_class(ArchiveHeapTestClass);
_dumped_interned_strings = new (ResourceObj::C_HEAP, mtClass)DumpedInternedStrings();
init_subgraph_entry_fields(CHECK);
}
}
void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
int num, bool is_closed_archive,
bool is_closed_archive,
bool is_full_module_graph) {
_num_total_subgraph_recordings = 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
// are restored by HeapShared::initialize_from_archived_subgraph().
int i;
for (i = 0; i < num; ) {
for (int i = 0; fields[i].valid(); ) {
ArchivableStaticFieldInfo* info = &fields[i];
const char* klass_name = info->klass_name;
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
// {start_recording_subgraph ... done_recording_subgraph} pass to
// save time.
for (; i < num; i++) {
for (; fields[i].valid(); i++) {
ArchivableStaticFieldInfo* f = &fields[i];
if (f->klass_name != klass_name) {
break;
@ -1501,7 +1681,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
log_info(cds, heap)(" Recorded %d klasses", _num_total_recorded_klasses);
#ifndef PRODUCT
for (int i = 0; i < num; i++) {
for (int i = 0; fields[i].valid(); i++) {
ArchivableStaticFieldInfo* f = &fields[i];
verify_subgraph_from_static_field(f->klass, f->offset);
}

View File

@ -45,13 +45,7 @@ class FileMapInfo;
class KlassSubGraphInfo;
class ResourceBitMap;
struct ArchivableStaticFieldInfo {
const char* klass_name;
const char* field_name;
InstanceKlass* klass;
int offset;
BasicType type;
};
struct ArchivableStaticFieldInfo;
// 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
@ -78,7 +72,7 @@ class KlassSubGraphInfo: public CHeapObj<mtClass> {
// used at runtime if JVMTI ClassFileLoadHook is enabled.
bool _has_non_early_klasses;
static bool is_non_early_klass(Klass* k);
static void check_allowed_klass(InstanceKlass* ik);
public:
KlassSubGraphInfo(Klass* k, bool is_full_module_graph) :
_k(k), _subgraph_object_klasses(NULL),
@ -229,7 +223,6 @@ private:
static void check_closed_region_object(InstanceKlass* k);
static CachedOopInfo make_cached_oop_info(oop orig_obj);
static void archive_object_subgraphs(ArchivableStaticFieldInfo fields[],
int num,
bool is_closed_archive,
bool is_full_module_graph);
@ -249,8 +242,7 @@ private:
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(ArchivableStaticFieldInfo fields[],
int num, TRAPS);
static void init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], TRAPS);
// UseCompressedOops only: Used by decode_from_archive
static address _narrow_oop_base;
@ -303,7 +295,7 @@ private:
static void copy_roots();
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 clear_archived_roots_of(Klass* k);
static const ArchivedKlassSubGraphInfoRecord*
@ -396,6 +388,7 @@ private:
// Run-time only
static void clear_root(int index);
static void setup_test_class(const char* test_class_name) PRODUCT_RETURN;
#endif // INCLUDE_CDS_JAVA_HEAP
public:
@ -423,6 +416,7 @@ private:
static oop to_requested_address(oop 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

View File

@ -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,
// 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.
void ModuleEntryTable::patch_javabase_entries(Handle module_handle) {
void ModuleEntryTable::patch_javabase_entries(JavaThread* current, Handle module_handle) {
if (module_handle.is_null()) {
fatal("Unable to patch the module field of classes loaded prior to "
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++) {
Klass* k = list->at(i);
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();
}

View File

@ -238,7 +238,7 @@ public:
static bool javabase_defined() { return ((_javabase_module != NULL) &&
(_javabase_module->module() != NULL)); }
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(ModuleClosure* closure);

View File

@ -237,7 +237,7 @@ static void define_javabase_module(Handle module_handle, jstring version, jstrin
// so no locking is needed.
// 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",
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());
// 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()) {
THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object");

View File

@ -1042,8 +1042,13 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name,
assert(scp_index >= 0, "must be");
SharedClassPathEntry* scp_entry = FileMapInfo::shared_path(scp_index);
if (!Universe::is_module_initialized()) {
assert(scp_entry != NULL && scp_entry->is_modules_image(),
"Loading non-bootstrap classes before the module system is initialized");
assert(scp_entry != NULL, "must be");
// 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");
return true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -117,14 +117,15 @@ void Symbol::set_permanent() {
// ------------------------------------------------------------------
// Symbol::index_of
//
// Finds if the given string is a substring of this symbol's utf8 bytes.
// Return -1 on failure. Otherwise return the first index where str occurs.
int Symbol::index_of_at(int i, const char* str, int len) const {
// Test if we have the give substring at or after the i-th char of this
// symbol's utf8 bytes.
// 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");
if (len <= 0) return 0;
char first_char = str[0];
if (substr_len <= 0) return 0;
char first_char = substr[0];
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;
if (scan > limit)
return -1;
@ -133,9 +134,9 @@ int Symbol::index_of_at(int i, const char* str, int len) const {
if (scan == NULL)
return -1; // not found
assert(scan >= bytes+i && scan <= limit, "scan oob");
if (len <= 2
? (char) scan[len-1] == str[len-1]
: memcmp(scan+1, str+1, len-1) == 0) {
if (substr_len <= 2
? (char) scan[substr_len-1] == substr[substr_len-1]
: memcmp(scan+1, substr+1, substr_len-1) == 0) {
return (int)(scan - bytes);
}
}

View File

@ -240,8 +240,8 @@ class Symbol : public MetaspaceObj {
return code_byte == char_at(position);
}
// Tests if the symbol starts with the given prefix.
int index_of_at(int i, const char* str, int len) const;
// Test if the symbol has the give substring at or after the i-th char.
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
// note that the ordering is not alfabetical

View File

@ -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;
}
}

View File

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