diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.cpp b/src/hotspot/share/classfile/classLoaderDataGraph.cpp index 72a1e78838f2e18f6ba4ce41c28f204e50252bc2..c1fb1d68f4b1b813d49ba4367d09634ab61f29c5 100644 --- a/src/hotspot/share/classfile/classLoaderDataGraph.cpp +++ b/src/hotspot/share/classfile/classLoaderDataGraph.cpp @@ -186,7 +186,7 @@ void ClassLoaderDataGraph::walk_metadata_and_clean_metaspaces() { // on the stack or in the code cache, so we only have to repeat the full walk if // they were found at that time. // TODO: have redefinition clean old methods out of the code cache. They still exist in some places. - bool walk_all_metadata = InstanceKlass::has_previous_versions_and_reset(); + bool walk_all_metadata = InstanceKlass::should_clean_previous_versions_and_reset(); MetadataOnStackMark md_on_stack(walk_all_metadata, /*redefinition_walk*/false); clean_deallocate_lists(walk_all_metadata); diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp index e5b51ac277cc3132470d481f5e182c19fb22ac19..e4b10f95656b9aae8ddcb887f01e927403bd7456 100644 --- a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp +++ b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp @@ -73,7 +73,7 @@ bool ClassLoaderDataGraph::should_clean_metaspaces_and_reset() { // Only clean metaspaces after full GC. bool do_cleaning = _safepoint_cleanup_needed; #if INCLUDE_JVMTI - do_cleaning = do_cleaning && (_should_clean_deallocate_lists || InstanceKlass::has_previous_versions()); + do_cleaning = do_cleaning && (_should_clean_deallocate_lists || InstanceKlass::should_clean_previous_versions()); #else do_cleaning = do_cleaning && _should_clean_deallocate_lists; #endif diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index fdee66a8866e315b746eb92712d8ca9af72165c0..2e9a5125014b0618ee1abe37110f9298e2693269 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -3941,18 +3941,18 @@ void InstanceKlass::set_init_state(ClassState state) { // Globally, there is at least one previous version of a class to walk // during class unloading, which is saved because old methods in the class // are still running. Otherwise the previous version list is cleaned up. -bool InstanceKlass::_has_previous_versions = false; +bool InstanceKlass::_should_clean_previous_versions = false; // Returns true if there are previous versions of a class for class // unloading only. Also resets the flag to false. purge_previous_version // will set the flag to true if there are any left, i.e., if there's any // work to do for next time. This is to avoid the expensive code cache // walk in CLDG::clean_deallocate_lists(). -bool InstanceKlass::has_previous_versions_and_reset() { - bool ret = _has_previous_versions; - log_trace(redefine, class, iklass, purge)("Class unloading: has_previous_versions = %s", +bool InstanceKlass::should_clean_previous_versions_and_reset() { + bool ret = _should_clean_previous_versions; + log_trace(redefine, class, iklass, purge)("Class unloading: should_clean_previous_versions = %s", ret ? "true" : "false"); - _has_previous_versions = false; + _should_clean_previous_versions = false; return ret; } @@ -4027,12 +4027,17 @@ void InstanceKlass::purge_previous_version_list() { version++; continue; } else { - log_trace(redefine, class, iklass, purge)("previous version " INTPTR_FORMAT " is alive", p2i(pv_node)); assert(pvcp->pool_holder() != NULL, "Constant pool with no holder"); guarantee (!loader_data->is_unloading(), "unloaded classes can't be on the stack"); live_count++; - // found a previous version for next time we do class unloading - _has_previous_versions = true; + if (pvcp->is_shared()) { + // Shared previous versions can never be removed so no cleaning is needed. + log_trace(redefine, class, iklass, purge)("previous version " PTR_FORMAT " is shared", p2i(pv_node)); + } else { + // Previous version alive, set that clean is needed for next time. + _should_clean_previous_versions = true; + log_trace(redefine, class, iklass, purge)("previous version " PTR_FORMAT " is alive", p2i(pv_node)); + } } // next previous version @@ -4132,13 +4137,19 @@ void InstanceKlass::add_previous_version(InstanceKlass* scratch_class, return; } - // Add previous version if any methods are still running. - // Set has_previous_version flag for processing during class unloading. - _has_previous_versions = true; - log_trace(redefine, class, iklass, add) ("scratch class added; one of its methods is on_stack."); + // Add previous version if any methods are still running or if this is + // a shared class which should never be removed. assert(scratch_class->previous_versions() == NULL, "shouldn't have a previous version"); scratch_class->link_previous_versions(previous_versions()); link_previous_versions(scratch_class); + if (cp_ref->is_shared()) { + log_trace(redefine, class, iklass, add) ("scratch class added; class is shared"); + } else { + // We only set clean_previous_versions flag for processing during class + // unloading for non-shared classes. + _should_clean_previous_versions = true; + log_trace(redefine, class, iklass, add) ("scratch class added; one of its methods is on_stack."); + } } // end add_previous_version() #endif // INCLUDE_JVMTI diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 23b773d0195bbf4e6522e76d04f244e3b7a6631e..9f03bc50d4886483418c30ec6b4f9b7f51030bc6 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -815,7 +815,7 @@ public: } private: - static bool _has_previous_versions; + static bool _should_clean_previous_versions; public: static void purge_previous_versions(InstanceKlass* ik) { if (ik->has_been_redefined()) { @@ -823,8 +823,8 @@ public: } } - static bool has_previous_versions_and_reset(); - static bool has_previous_versions() { return _has_previous_versions; } + static bool should_clean_previous_versions_and_reset(); + static bool should_clean_previous_versions() { return _should_clean_previous_versions; } // JVMTI: Support for caching a class file before it is modified by an agent that can do retransformation void set_cached_class_file(JvmtiCachedClassFileData *data) { @@ -844,7 +844,7 @@ public: #else // INCLUDE_JVMTI static void purge_previous_versions(InstanceKlass* ik) { return; }; - static bool has_previous_versions_and_reset() { return false; } + static bool should_clean_previous_versions_and_reset() { return false; } void set_cached_class_file(JvmtiCachedClassFileData *data) { assert(data == NULL, "unexpected call with JVMTI disabled"); diff --git a/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefinePreviousVersions.java b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefinePreviousVersions.java index 12aefba51e5bc94aa916112fc7e7f162e7b68334..d6dc502656b511ba8c29924024758afc3a64133c 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefinePreviousVersions.java +++ b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefinePreviousVersions.java @@ -24,7 +24,7 @@ /* * @test * @bug 8165246 8010319 - * @summary Test has_previous_versions flag and processing during class unloading. + * @summary Test clean_previous_versions flag and processing during class unloading. * @requires vm.jvmti * @requires vm.opt.final.ClassUnloading * @requires vm.flagless @@ -86,15 +86,15 @@ public class RedefinePreviousVersions { "-Xlog:redefine+class+iklass+add=trace,redefine+class+iklass+purge=trace", "RedefinePreviousVersions"); new OutputAnalyzer(pb.start()) - .shouldContain("Class unloading: has_previous_versions = false") - .shouldContain("Class unloading: has_previous_versions = true") + .shouldContain("Class unloading: should_clean_previous_versions = false") + .shouldContain("Class unloading: should_clean_previous_versions = true") .shouldHaveExitValue(0); return; } // Redefine a class and create some garbage // Since there are no methods running, the previous version is never added to the - // previous_version_list and the flag _has_previous_versions should stay false + // previous_version_list and the flag _should_clean_previous_versions should stay false RedefineClassHelper.redefineClass(RedefinePreviousVersions_B.class, newB); for (int i = 0; i < 10 ; i++) { @@ -114,7 +114,7 @@ public class RedefinePreviousVersions { } // Since a method of newRunning is running, this class should be added to the previous_version_list - // of Running, and _has_previous_versions should return true at class unloading. + // of Running, and _should_clean_previous_versions should return true at class unloading. RedefineClassHelper.redefineClass(RedefinePreviousVersions_Running.class, newRunning); for (int i = 0; i < 10 ; i++) { diff --git a/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineSharedClassJFR.java b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineSharedClassJFR.java new file mode 100644 index 0000000000000000000000000000000000000000..6ee54be7831af4ef326f7c00203cfd594d04284f --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineSharedClassJFR.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023, 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 8306929 + * @summary Verify should_clean_previous_versions when run with JFR and CDS + * @requires vm.jvmti + * @requires vm.cds + * @requires vm.hasJFR + * @requires vm.opt.final.ClassUnloading + * @requires vm.flagless + * @library /test/lib + * @run driver RedefineSharedClassJFR xshare-off + * @run driver RedefineSharedClassJFR xshare-on + */ +import java.util.ArrayList; +import java.util.List; + +import jdk.test.lib.Platform; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +import jtreg.SkippedException; + +public class RedefineSharedClassJFR { + + private static final String SHOULD_CLEAN_TRUE = "Class unloading: should_clean_previous_versions = true"; + private static final String SHOULD_CLEAN_FALSE = "Class unloading: should_clean_previous_versions = false"; + private static final String SCRATCH_CLASS_ADDED_SHARED = "scratch class added; class is shared"; + private static final String SCRATCH_CLASS_ADDED_ON_STACK = "scratch class added; one of its methods is on_stack."; + + public static void main(String[] args) throws Exception { + // Skip test if default archive is supported. + if (!Platform.isDefaultCDSArchiveSupported()) { + throw new SkippedException("Supported platform"); + } + + // Test is run with JFR which will transform a number of classes. Depending + // on if the test is run with or without CDS the output will be different, + // due to the fact that shared classes can never be cleaned out after retranform. + if (args.length > 0) { + // When run with an argument the class is used as driver and should parse + // the output to verify it is correct given the command line. + List<String> baseCommand = List.of( + "-XX:StartFlightRecording", + "-Xlog:redefine+class+iklass+add=trace,redefine+class+iklass+purge=trace", + "RedefineSharedClassJFR"); + + if (args[0].equals("xshare-off")) { + // First case is with -Xshare:off. In this case no classes are shared + // and we should be able to clean out the retransformed classes. There + // is no guarantee that any classes will be in use, so just verify that + // no classes are added due to being shared. + List<String> offCommand = new ArrayList<>(); + offCommand.add("-Xshare:off"); + offCommand.addAll(baseCommand); + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(offCommand); + new OutputAnalyzer(pb.start()) + // We can't expect any of the transformed classes to be in use + // so the only thing we can verify is that no scratch classes + // are added because they are shared. + .shouldNotContain(SCRATCH_CLASS_ADDED_SHARED) + .shouldHaveExitValue(0); + return; + } else if (args[0].equals("xshare-on")) { + // With -Xshare:on, the shared classes can never be cleaned out. Check the + // logs to verify we don't try to clean when we know it is not needed. + List<String> onCommand = new ArrayList<>(); + onCommand.add("-Xshare:on"); + onCommand.addAll(baseCommand); + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(onCommand); + new OutputAnalyzer(pb.start()) + .shouldContain(SHOULD_CLEAN_FALSE) + .shouldNotContain(SHOULD_CLEAN_TRUE) + .shouldContain(SCRATCH_CLASS_ADDED_SHARED) + // If the below line occurs, then should_clean_previous_versions will be + // true and the above shouldContain will trigger. This check is to + // show the intention that we don't expect any non-shared transformed + // classes to be in use. + .shouldNotContain(SCRATCH_CLASS_ADDED_ON_STACK) + .shouldHaveExitValue(0); + return; + } + } + + // When run without any argument this class acts as test and we do a system GC + // to trigger cleaning and get the output we want to check. + System.gc(); + } +}