--- old/src/hotspot/share/memory/metaspaceShared.cpp 2020-03-04 20:50:57.763568721 -0800 +++ new/src/hotspot/share/memory/metaspaceShared.cpp 2020-03-04 20:50:57.110543065 -0800 @@ -1691,10 +1691,11 @@ } class LinkSharedClassesClosure : public KlassClosure { + bool _is_static; Thread* THREAD; bool _made_progress; public: - LinkSharedClassesClosure(Thread* thread) : THREAD(thread), _made_progress(false) {} + LinkSharedClassesClosure(bool is_static, Thread* thread) : _is_static(is_static), THREAD(thread), _made_progress(false) {} void reset() { _made_progress = false; } bool made_progress() const { return _made_progress; } @@ -1702,21 +1703,25 @@ void do_klass(Klass* k) { if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - // Link the class to cause the bytecodes to be rewritten and the - // cpcache to be created. Class verification is done according - // to -Xverify setting. - _made_progress |= MetaspaceShared::try_link_class(ik, THREAD); - guarantee(!HAS_PENDING_EXCEPTION, "exception in link_class"); + // For dynamic CDS dump, only link classes loaded by the builtin class loaders. + bool do_linking = _is_static ? true : ik->loader_type() != 0; + if (do_linking) { + // Link the class to cause the bytecodes to be rewritten and the + // cpcache to be created. Class verification is done according + // to -Xverify setting. + _made_progress |= MetaspaceShared::try_link_class(ik, THREAD); + guarantee(!HAS_PENDING_EXCEPTION, "exception in link_class"); - ik->constants()->resolve_class_constants(THREAD); + ik->constants()->resolve_class_constants(THREAD); + } } } }; -void MetaspaceShared::link_and_cleanup_shared_classes(TRAPS) { +void MetaspaceShared::link_and_cleanup_shared_classes(bool is_static, TRAPS) { // We need to iterate because verification may cause additional classes // to be loaded. - LinkSharedClassesClosure link_closure(THREAD); + LinkSharedClassesClosure link_closure(is_static, THREAD); do { link_closure.reset(); ClassLoaderDataGraph::unlocked_loaded_classes_do(&link_closure); @@ -1792,7 +1797,7 @@ // were not explicitly specified in the classlist. E.g., if an interface implemented by class K // fails verification, all other interfaces that were not specified in the classlist but // are implemented by K are not verified. - link_and_cleanup_shared_classes(CATCH); + link_and_cleanup_shared_classes(true, CATCH); log_info(cds)("Rewriting and linking classes: done"); if (HeapShared::is_heap_object_archiving_allowed()) { @@ -1848,7 +1853,7 @@ // Returns true if the class's status has changed bool MetaspaceShared::try_link_class(InstanceKlass* ik, TRAPS) { - assert(DumpSharedSpaces, "should only be called during dumping"); + Arguments::assert_is_dumping_archive(); if (ik->init_state() < InstanceKlass::linked && !SystemDictionaryShared::has_class_failed_verification(ik)) { bool saved = BytecodeVerificationLocal; --- old/src/hotspot/share/memory/metaspaceShared.hpp 2020-03-04 20:50:59.049619248 -0800 +++ new/src/hotspot/share/memory/metaspaceShared.hpp 2020-03-04 20:50:58.401593789 -0800 @@ -296,7 +296,7 @@ } static bool try_link_class(InstanceKlass* ik, TRAPS); - static void link_and_cleanup_shared_classes(TRAPS); + static void link_and_cleanup_shared_classes(bool is_static, TRAPS); #if INCLUDE_CDS static ReservedSpace reserve_shared_space(size_t size, char* requested_address = NULL); --- old/src/hotspot/share/oops/constantPool.cpp 2020-03-04 20:51:00.309668754 -0800 +++ new/src/hotspot/share/oops/constantPool.cpp 2020-03-04 20:50:59.660643255 -0800 @@ -304,7 +304,7 @@ } void ConstantPool::resolve_class_constants(TRAPS) { - assert(DumpSharedSpaces, "used during dump time only"); + Arguments::assert_is_dumping_archive(); // The _cache may be NULL if the _pool_holder klass fails verification // at dump time due to missing dependencies. if (cache() == NULL || reference_map() == NULL) { --- old/src/hotspot/share/oops/instanceKlass.hpp 2020-03-04 20:51:01.606719714 -0800 +++ new/src/hotspot/share/oops/instanceKlass.hpp 2020-03-04 20:51:00.961694371 -0800 @@ -1263,12 +1263,6 @@ public: u2 idnum_allocated_count() const { return _idnum_allocated_count; } -public: - void set_in_error_state() { - assert(DumpSharedSpaces, "only call this when dumping archive"); - _init_state = initialization_error; - } - private: // initialization state void set_init_state(ClassState state); --- old/src/hotspot/share/prims/jvm.cpp 2020-03-04 20:51:02.874769533 -0800 +++ new/src/hotspot/share/prims/jvm.cpp 2020-03-04 20:51:02.219743798 -0800 @@ -469,6 +469,10 @@ JVM_ENTRY_NO_ENV(void, JVM_BeforeHalt()) JVMWrapper("JVM_BeforeHalt"); + // Link all classes for dynamic CDS dumping before vm exit. + if (DynamicDumpSharedSpaces) { + MetaspaceShared::link_and_cleanup_shared_classes(false, THREAD); + } EventShutdown event; if (event.should_commit()) { event.set_reason("Shutdown requested from Java"); --- old/src/hotspot/share/runtime/thread.cpp 2020-03-04 20:51:04.161820100 -0800 +++ new/src/hotspot/share/runtime/thread.cpp 2020-03-04 20:51:03.509794483 -0800 @@ -4326,6 +4326,13 @@ void JavaThread::invoke_shutdown_hooks() { HandleMark hm(this); + // Link all classes for dynamic CDS dumping before vm exit. + // Same operation is being done in JVM_BeforeHalt for handling the + // case where the application calls System.exit(). + if (DynamicDumpSharedSpaces) { + MetaspaceShared::link_and_cleanup_shared_classes(false, this); + } + // We could get here with a pending exception, if so clear it now. if (this->has_pending_exception()) { this->clear_pending_exception(); --- /dev/null 2019-10-31 12:50:31.827000000 -0700 +++ new/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/LinkClassTest.java 2020-03-04 20:51:04.867847839 -0800 @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020, 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 + * @summary Classes loaded by the builtin class loaders should be linked + * during dynamic CDS dump time. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * /test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/test-classes + * @build LinkClassApp + * @run driver ClassFileInstaller -jar link_class_app.jar LinkClassApp Parent Child Parent2 Child2 MyShutdown + * @run driver LinkClassTest + */ + +public class LinkClassTest extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(LinkClassTest::test); + } + + static void test() throws Exception { + String topArchiveName = getNewArchiveName(); + String appJar = ClassFileInstaller.getJarPath("link_class_app.jar"); + String mainClass = "LinkClassApp"; + + // dump archive with the app without calling System.exit(). + dump(topArchiveName, + "-Xlog:class+load,cds+dynamic=info,cds", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldNotContain("Skipping Parent: Not linked") + .shouldNotContain("Skipping Parent2: Not linked") + .shouldNotContain("Skipping Child: Not linked") + .shouldNotContain("Skipping Child2: Not linked") + .shouldHaveExitValue(0); + }); + + run(topArchiveName, + "-Xlog:class+load", + "-cp", appJar, mainClass, "run") + .assertNormalExit(output -> { + output.shouldContain("Parent source: shared objects file (top)") + .shouldContain("Parent2 source: shared objects file (top)") + .shouldContain("Child source: shared objects file (top)") + .shouldContain("Child2 source: shared objects file (top)") + .shouldHaveExitValue(0); + }); + + // dump archive with the app calling System.exit(). + dump(topArchiveName, + "-Xlog:class+load,cds+dynamic=info,cds", + "-cp", appJar, mainClass, "callExit") + .assertNormalExit(output -> { + output.shouldNotContain("Skipping Parent: Not linked") + .shouldNotContain("Skipping Parent2: Not linked") + .shouldNotContain("Skipping Child: Not linked") + .shouldNotContain("Skipping Child2: Not linked") + .shouldHaveExitValue(0); + }); + + run(topArchiveName, + "-Xlog:class+load", + "-cp", appJar, mainClass, "run") + .assertNormalExit(output -> { + output.shouldContain("Parent source: shared objects file (top)") + .shouldContain("Parent2 source: shared objects file (top)") + .shouldContain("Child source: shared objects file (top)") + .shouldContain("Child2 source: shared objects file (top)") + .shouldHaveExitValue(0); + }); + } +} --- /dev/null 2019-10-31 12:50:31.827000000 -0700 +++ new/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/test-classes/LinkClassApp.java 2020-03-04 20:51:06.151898287 -0800 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020, 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. + */ + +class Parent { + int get() {return 1;} +} + +class Child extends Parent { + int get() {return 2;} + int dummy() { + // When Child is linked during dynamic dump (when the VM is shutting + // down), it will be verified, which will recursively cause Child2/Parent2 + // to be loaded and verified. + // We want to make sure that this is done at a point in the VM lifecycle where + // it's still safe to do so. + Parent2 x = new Child2(); + return x.get(); + } +} + +class Parent2 { + int get() {return 3;} +} + +class Child2 extends Parent2 { + int get() {return 4;} +} + +class MyShutdown extends Thread{ + public void run(){ + System.out.println("shut down hook invoked..."); + } +} + +class LinkClassApp { + public static void main(String args[]) { + Runtime r=Runtime.getRuntime(); + r.addShutdownHook(new MyShutdown()); + + if (args.length > 0 && args[0].equals("run")) { + System.out.println("test() = " + test()); + } else { + // Executed during dynamic dumping. + System.out.println("Test.class is initialized."); + System.out.println("Parent.class and Child.class are loaded when Test.class is verified,"); + System.out.println("but these two classes are not linked"); + } + + if (args.length > 0 && args[0].equals("callExit")) { + System.exit(0); + } + } + + static int test() { + // Verification of Test.test() would load Child and Parent, and create a verification constraint that + // Child must be a subtype of Parent. + // + // Child and Parent are not linked until Test.test() is actually executed. + Parent x = new Child(); + return x.get(); + } +}