8293182: Improve testing of CDS archive heap
Reviewed-by: ccheung, coleenp
This commit is contained in:
parent
51de765867
commit
f84386cf6e
@ -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") \
|
||||
|
@ -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(©);
|
||||
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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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…
Reference in New Issue
Block a user