Skip to content
Permalink
Browse files
8261090: Store old classfiles in static CDS archive
Reviewed-by: iklam, minqi
  • Loading branch information
calvinccheung committed Apr 22, 2021
1 parent 159f5e1 commit 9499175064a8073f37a63a2696fb47f26ae89865
Showing 29 changed files with 895 additions and 42 deletions.
@@ -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;
@@ -285,7 +285,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()) &&

// 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");
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,31 @@ class CollectCLDClosure : public CLDClosure {
ClassLoaderData* cld_at(int index) { return _loaded_cld.at(index); }
};

// Check if a class or its super class/interface is old.
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 +784,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 +830,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 {

1 comment on commit 9499175

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 9499175 Apr 22, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.