Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

8261090: Store old classfiles in static CDS archive #3479

Closed
@@ -5920,18 +5920,6 @@ void ClassFileParser::parse_stream(const ClassFileStream* const stream,
_minor_version = stream->get_u2_fast();
_major_version = stream->get_u2_fast();

if (DumpSharedSpaces && _major_version < JAVA_6_VERSION) {
ResourceMark rm;
warning("Pre JDK 6 class not supported by CDS: %u.%u %s",
_major_version, _minor_version, _class_name->as_C_string());
Exceptions::fthrow(
THREAD_AND_LOCATION,
vmSymbols::java_lang_UnsupportedClassVersionError(),
"Unsupported major.minor version for dump time %u.%u",
_major_version,
_minor_version);
}

// Check version numbers - we check this even with verifier off
verify_class_version(_major_version, _minor_version, _class_name, CHECK);

@@ -1380,18 +1380,28 @@ bool SystemDictionaryShared::should_be_excluded(InstanceKlass* k) {
// class loader doesn't expect it.
if (has_class_failed_verification(k)) {
warn_excluded(k, "Failed verification");
return true;
} else {
warn_excluded(k, "Not linked");
if (!MetaspaceShared::is_old_class(k)) {
warn_excluded(k, "Not linked");
return true;
}
}
return true;
}
if (k->major_version() < 50 /*JAVA_6_VERSION*/) {
if (DynamicDumpSharedSpaces && k->major_version() < 50 /*JAVA_6_VERSION*/) {
// In order to support old classes during dynamic dump, class rewriting needs to
// be reverted. This would result in more complex code and testing but not much gain.
ResourceMark rm;
log_warning(cds)("Pre JDK 6 class not supported by CDS: %u.%u %s",
k->major_version(), k->minor_version(), k->name()->as_C_string());
return true;
}

if (MetaspaceShared::is_old_class(k) && k->is_linked()) {
warn_excluded(k, "Old class has been linked");
return true;
}

InstanceKlass* super = k->java_super();
if (super != NULL && should_be_excluded(super)) {
ResourceMark rm;
@@ -39,6 +39,7 @@
#include "interpreter/bytecodeStream.hpp"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "memory/metaspaceShared.hpp"
#include "memory/oopFactory.hpp"
#include "memory/resourceArea.hpp"
#include "memory/universe.hpp"
@@ -285,7 +286,9 @@ bool Verifier::is_eligible_for_verification(InstanceKlass* klass, bool should_ve
// already been rewritten to contain constant pool cache indices,
// which the verifier can't understand.
// Shared classes shouldn't have stackmaps either.
!klass->is_shared() &&
// However, bytecodes for shared old classes can be verified because
// they have not been rewritten.
(!(klass->is_shared() && klass->is_rewritten())) &&
Copy link
Contributor

@yminqi yminqi Apr 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the outmost brackets are not necessary. Could your remove it?

Copy link
Member Author

@calvinccheung calvinccheung Apr 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I also removed the extra include of metaspaceShared.hpp.


// As of the fix for 4486457 we disable verification for all of the
// dynamically-generated bytecodes associated with the 1.4
@@ -28,6 +28,7 @@
#include "interpreter/interpreter.hpp"
#include "interpreter/rewriter.hpp"
#include "memory/metadataFactory.hpp"
#include "memory/metaspaceShared.hpp"
#include "memory/resourceArea.hpp"
#include "oops/constantPool.hpp"
#include "oops/generateOopMap.hpp"
@@ -567,8 +568,9 @@ void Rewriter::rewrite_bytecodes(TRAPS) {
}

void Rewriter::rewrite(InstanceKlass* klass, TRAPS) {
if (!DumpSharedSpaces) {
assert(!klass->is_shared(), "archive methods must not be rewritten at run time");
if (klass->is_shared()) {
assert(!klass->is_rewritten(), "rewritten shared classes cannot be rewritten again");
Copy link
Member

@iklam iklam Apr 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add

assert(MetaspaceShared::is_old_class(klass), "only shared old classes aren't rewritten");

Copy link
Member Author

@calvinccheung calvinccheung Apr 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

assert(MetaspaceShared::is_old_class(klass), "only shared old classes aren't rewritten");
}
ResourceMark rm(THREAD);
constantPoolHandle cpool(THREAD, klass->constants());
@@ -389,7 +389,9 @@ static void rewrite_nofast_bytecode(const methodHandle& method) {
void MetaspaceShared::rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread* thread, InstanceKlass* ik) {
for (int i = 0; i < ik->methods()->length(); i++) {
methodHandle m(thread, ik->methods()->at(i));
rewrite_nofast_bytecode(m);
if (!is_old_class(ik)) {
rewrite_nofast_bytecode(m);
}
Fingerprinter fp(m);
// The side effect of this call sets method's fingerprint field.
fp.fingerprint();
@@ -578,9 +580,30 @@ class CollectCLDClosure : public CLDClosure {
ClassLoaderData* cld_at(int index) { return _loaded_cld.at(index); }
};

bool MetaspaceShared::is_old_class(InstanceKlass* ik) {
if (ik == NULL) {
return false;
}
if (ik->major_version() < 50 /*JAVA_6_VERSION*/) {
return true;
}
if (is_old_class(ik->java_super())) {
return true;
}
Array<InstanceKlass*>* interfaces = ik->local_interfaces();
int len = interfaces->length();
for (int i = 0; i < len; i++) {
if (is_old_class(interfaces->at(i))) {
return true;
}
}
return false;
}

bool MetaspaceShared::linking_required(InstanceKlass* ik) {
// For static CDS dump, do not link old classes.
// For dynamic CDS dump, only link classes loaded by the builtin class loaders.
return DumpSharedSpaces ? true : !ik->is_shared_unregistered_class();
return DumpSharedSpaces ? !MetaspaceShared::is_old_class(ik) : !ik->is_shared_unregistered_class();
}

bool MetaspaceShared::link_class_for_cds(InstanceKlass* ik, TRAPS) {
@@ -760,7 +783,7 @@ bool MetaspaceShared::try_link_class(Thread* current, InstanceKlass* ik) {
ExceptionMark em(current);
Thread* THREAD = current; // For exception macros.
Arguments::assert_is_dumping_archive();
if (ik->is_loaded() && !ik->is_linked() &&
if (ik->is_loaded() && !ik->is_linked() && !MetaspaceShared::is_old_class(ik) &&
!SystemDictionaryShared::has_class_failed_verification(ik)) {
bool saved = BytecodeVerificationLocal;
if (ik->is_shared_unregistered_class() && ik->class_loader() == NULL) {
@@ -806,7 +829,9 @@ void VM_PopulateDumpSharedSpace::dump_java_heap_objects(GrowableArray<Klass*>* k
Klass* k = klasses->at(i);
if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
ik->constants()->add_dumped_interned_strings();
if (ik->is_linked()) {
ik->constants()->add_dumped_interned_strings();
}
}
}
if (_extra_interned_strings != NULL) {
@@ -137,6 +137,7 @@ class MetaspaceShared : AllStatic {
static void link_and_cleanup_shared_classes(TRAPS) NOT_CDS_RETURN;
static bool link_class_for_cds(InstanceKlass* ik, TRAPS) NOT_CDS_RETURN_(false);
static bool linking_required(InstanceKlass* ik) NOT_CDS_RETURN_(false);
static bool is_old_class(InstanceKlass* ik);

#if INCLUDE_CDS
// Alignment for the 3 core CDS regions (MC/RW/RO) only.
@@ -405,7 +405,11 @@ void ConstMethod::copy_annotations_from(ClassLoaderData* loader_data, ConstMetho
void ConstMethod::metaspace_pointers_do(MetaspaceClosure* it) {
log_trace(cds)("Iter(ConstMethod): %p", this);

it->push(&_constants);
if (!method()->method_holder()->is_rewritten()) {
it->push(&_constants, MetaspaceClosure::_writable);
} else {
it->push(&_constants);
}
it->push(&_stackmap_data);
if (has_method_annotations()) {
it->push(method_annotations_addr());
@@ -419,7 +423,6 @@ void ConstMethod::metaspace_pointers_do(MetaspaceClosure* it) {
if (has_default_annotations()) {
it->push(default_annotations_addr());
}
ConstMethod* this_ptr = this;
}

// Printing
@@ -353,6 +353,9 @@ void ConstantPool::add_dumped_interned_strings() {

// CDS support. Create a new resolved_references array.
void ConstantPool::restore_unshareable_info(TRAPS) {
if (!_pool_holder->is_linked() && !_pool_holder->is_rewritten()) {
return;
}
assert(is_constantPool(), "ensure C++ vtable is restored");
assert(on_stack(), "should always be set for shared constant pools");
assert(is_shared(), "should always be set for shared constant pools");
@@ -390,6 +393,9 @@ void ConstantPool::restore_unshareable_info(TRAPS) {
}

void ConstantPool::remove_unshareable_info() {
if (!_pool_holder->is_linked() && _pool_holder->is_shared_old_klass()) {
return;
}
// Resolved references are not in the shared archive.
// Save the length for restoration. It is not necessarily the same length
// as reference_map.length() if invokedynamic is saved. It is needed when
@@ -2454,7 +2454,11 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) {

it->push(&_annotations);
it->push((Klass**)&_array_klasses);
it->push(&_constants);
if (!is_rewritten()) {
it->push(&_constants, MetaspaceClosure::_writable);
} else {
it->push(&_constants);
}
it->push(&_inner_classes);
#if INCLUDE_JVMTI
it->push(&_previous_versions);
@@ -2491,6 +2495,12 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) {
}

void InstanceKlass::remove_unshareable_info() {

if (MetaspaceShared::is_old_class(this)) {
// Set the old class bit.
set_is_shared_old_klass();
}

Klass::remove_unshareable_info();

if (SystemDictionaryShared::has_class_failed_verification(this)) {
@@ -177,7 +177,8 @@ class Klass : public Metadata {
u2 _shared_class_flags;
enum {
_archived_lambda_proxy_is_available = 2,
_has_value_based_class_annotation = 4
_has_value_based_class_annotation = 4,
_is_shared_old_klass = 8
};
#endif

@@ -333,6 +334,14 @@ class Klass : public Metadata {
NOT_CDS(return false;)
}

void set_is_shared_old_klass() {
CDS_ONLY(_shared_class_flags |= _is_shared_old_klass;)
}
bool is_shared_old_klass() const {
CDS_ONLY(return (_shared_class_flags & _is_shared_old_klass) != 0;)
NOT_CDS(return false;)
}


// Obtain the module or package for this class
virtual ModuleEntry* module() const = 0;
@@ -51,7 +51,7 @@ inline InstanceKlass* klassVtable::ik() const {
}

bool klassVtable::is_preinitialized_vtable() {
return _klass->is_shared() && !MetaspaceShared::remapped_readwrite();
return _klass->is_shared() && !MetaspaceShared::remapped_readwrite() && !_klass->is_shared_old_klass();
}


@@ -1093,7 +1093,8 @@ void itableMethodEntry::initialize(Method* m) {

#ifdef ASSERT
if (MetaspaceShared::is_in_shared_metaspace((void*)&_method) &&
!MetaspaceShared::remapped_readwrite()) {
!MetaspaceShared::remapped_readwrite() &&
!MetaspaceShared::is_old_class(m->method_holder())) {
// At runtime initialize_itable is rerun as part of link_class_impl()
// for a shared class loaded by the non-boot loader.
// The dumptime itable method entry should be the same as the runtime entry.
@@ -344,11 +344,13 @@ Symbol* Method::klass_name() const {
void Method::metaspace_pointers_do(MetaspaceClosure* it) {
log_trace(cds)("Iter(Method): %p", this);

it->push(&_constMethod);
if (!method_holder()->is_rewritten()) {
it->push(&_constMethod, MetaspaceClosure::_writable);
} else {
it->push(&_constMethod);
}
it->push(&_method_data);
it->push(&_method_counters);

Method* this_ptr = this;
}

// Attempt to return method to original state. Clear any pointers
@@ -362,7 +364,7 @@ void Method::remove_unshareable_info() {
}

void Method::set_vtable_index(int index) {
if (is_shared() && !MetaspaceShared::remapped_readwrite()) {
if (is_shared() && !MetaspaceShared::remapped_readwrite() && !method_holder()->is_shared_old_klass()) {
// At runtime initialize_vtable is rerun as part of link_class_impl()
// for a shared class loaded by the non-boot loader to obtain the loader
// constraints based on the runtime classloaders' context.
@@ -373,7 +375,7 @@ void Method::set_vtable_index(int index) {
}

void Method::set_itable_index(int index) {
if (is_shared() && !MetaspaceShared::remapped_readwrite()) {
if (is_shared() && !MetaspaceShared::remapped_readwrite() && !method_holder()->is_shared_old_klass()) {
// At runtime initialize_itable is rerun as part of link_class_impl()
// for a shared class loaded by the non-boot loader to obtain the loader
// constraints based on the runtime classloaders' context. The dumptime
@@ -68,7 +68,7 @@ public static void main(String[] args) throws Exception {
.addSuffix(mainClass);
OutputAnalyzer output = CDSTestUtils.runWithArchive(runOpts);
output.shouldContain("[class,load] LambdaWithOldClassApp source: shared objects file")
.shouldMatch(".class.load. LambdaWithOldClassApp[$][$]Lambda[$].*/0x.*source:.*LambdaWithOldClassApp")
.shouldMatch(".class.load. LambdaWithOldClassApp[$][$]Lambda[$].*/0x.*source:.*shared objects file")
.shouldHaveExitValue(0);
}
}
@@ -24,7 +24,7 @@

/*
* @test
* @summary classes with major version < JDK_6 (50) should not be included in CDS
* @summary CDS support of old classes with major version < JDK_6 (50) for static archive.
* @requires vm.cds
* @library /test/lib
* @modules java.base/jdk.internal.org.objectweb.asm
@@ -35,6 +35,7 @@

import java.io.File;
import java.io.FileOutputStream;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.process.OutputAnalyzer;
import java.nio.file.Files;

@@ -55,26 +56,47 @@ public static void main(String[] args) throws Exception {
}

String appClasses[] = TestCommon.list("Hello");
boolean dynamicMode = CDSTestUtils.DYNAMIC_DUMP;

// CASE 1: pre-JDK 6 compiled classes should be excluded from the dump
OutputAnalyzer output = TestCommon.dump(jar, appClasses);
TestCommon.checkExecReturn(output, 0, true, "Pre JDK 6 class not supported by CDS");
OutputAnalyzer output = TestCommon.dump(jar, appClasses, "-Xlog:class+load,cds=debug");
TestCommon.checkExecReturn(output, 0,
dynamicMode ? true : false,
"Pre JDK 6 class not supported by CDS");

TestCommon.run(
"-cp", jar,
"-Xlog:class+load",
"Hello")
.assertNormalExit("Hello Unicode world (Old)");
.assertNormalExit(out -> {
out.shouldContain("Hello Unicode world (Old)");
if (!dynamicMode) {
out.shouldContain("Hello source: shared objects file");
} else {
out.shouldMatch(".class.load. Hello source:.*OldClassTest_old.jar");
}
});

// CASE 2: if we exlcude old version of this class, we should not pick up
// the newer version of this class in a subsequent classpath element.
String classpath = jar + File.pathSeparator + jarSrcFile.getPath();
output = TestCommon.dump(classpath, appClasses);
TestCommon.checkExecReturn(output, 0, true, "Pre JDK 6 class not supported by CDS");
TestCommon.checkExecReturn(output, 0,
dynamicMode ? true : false,
"Pre JDK 6 class not supported by CDS");

TestCommon.run(
"-cp", classpath,
"-Xlog:class+load",
"Hello")
.assertNormalExit("Hello Unicode world (Old)");
.assertNormalExit(out -> {
out.shouldContain("Hello Unicode world (Old)");
if (!dynamicMode) {
out.shouldContain("Hello source: shared objects file");
} else {
out.shouldMatch(".class.load. Hello source:.*OldClassTest_old.jar");
}
});
}

static void createTestJarFile(File jarSrcFile, File jarFile) throws Exception {