diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 3c41133e572..f60ce9b15b4 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -868,7 +868,7 @@ void G1CollectedHeap::prepare_for_mutator_after_full_collection(size_t allocatio // Rebuild the code root lists for each region rebuild_code_roots(); - finish_codecache_marking_cycle(); + CodeCache::arm_all_nmethods(); start_new_collection_set(); _allocator->init_mutator_alloc_regions(); @@ -3342,8 +3342,3 @@ void G1CollectedHeap::start_codecache_marking_cycle_if_inactive(bool concurrent_ CodeCache::arm_all_nmethods(); } } - -void G1CollectedHeap::finish_codecache_marking_cycle() { - CodeCache::on_gc_marking_cycle_finish(); - CodeCache::arm_all_nmethods(); -} diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index cb466a5e120..672dea9b7b0 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -953,7 +953,6 @@ public: void fill_with_dummy_object(HeapWord* start, HeapWord* end, bool zap) override; static void start_codecache_marking_cycle_if_inactive(bool concurrent_mark_start); - static void finish_codecache_marking_cycle(); // The shared block offset table array. G1BlockOffsetTable* bot() const { return _bot; } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 233901c30f8..73a697f8c51 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -24,6 +24,7 @@ #include "classfile/classLoaderData.hpp" #include "classfile/classLoaderDataGraph.hpp" +#include "code/codeCache.hpp" #include "cppstdlib/new.hpp" #include "gc/g1/g1BarrierSet.hpp" #include "gc/g1/g1BatchedTask.hpp" @@ -1378,6 +1379,8 @@ void G1ConcurrentMark::remark() { if (mark_finished) { weak_refs_work(); + CodeCache::on_gc_marking_cycle_finish(); + // Unload Klasses, String, Code Cache, etc. if (ClassUnloadingWithConcurrentMark) { G1CMIsAliveClosure is_alive(this); @@ -1446,7 +1449,7 @@ void G1ConcurrentMark::remark() { // Completely reset the marking state (except bitmaps) since marking completed. reset_at_marking_complete(); - G1CollectedHeap::finish_codecache_marking_cycle(); + CodeCache::arm_all_nmethods(); { GCTraceTime(Debug, gc, phases) debug("Report Object Count", _gc_timer_cm); diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index c5af4a8220b..1e838b344b7 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -23,6 +23,7 @@ */ #include "classfile/classLoaderDataGraph.hpp" +#include "code/codeCache.hpp" #include "cppstdlib/new.hpp" #include "gc/g1/g1CollectedHeap.hpp" #include "gc/g1/g1FullCollector.inline.hpp" @@ -330,6 +331,8 @@ void G1FullCollector::phase1_mark_live_objects() { assert(marker(0)->task_queue()->is_empty(), "Should be no oops on the stack"); } + CodeCache::on_gc_marking_cycle_finish(); + { GCTraceTime(Debug, gc, phases) debug("Phase 1: Flush Mark Stats Cache", scope()->timer()); for (uint i = 0; i < workers(); i++) { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index ff757f205a2..777b734c59e 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -644,7 +644,6 @@ void PSParallelCompact::post_compact() GCTraceTime(Info, gc, phases) tm("Post Compact", &_gc_timer); ParCompactionManager::remove_all_shadow_regions(); - CodeCache::on_gc_marking_cycle_finish(); CodeCache::arm_all_nmethods(); // Need to clear claim bits for the next full-gc (marking and adjust-pointers). @@ -1216,6 +1215,7 @@ void PSParallelCompact::marking_phase(ParallelOldTracer *gc_tracer) { // This is the point where the entire marking should have completed. ParCompactionManager::verify_all_marking_stack_empty(); + CodeCache::on_gc_marking_cycle_finish(); { GCTraceTime(Debug, gc, phases) tm("Weak Processing", &_gc_timer); diff --git a/src/hotspot/share/gc/serial/serialFullGC.cpp b/src/hotspot/share/gc/serial/serialFullGC.cpp index 13532dea07d..a88a0878305 100644 --- a/src/hotspot/share/gc/serial/serialFullGC.cpp +++ b/src/hotspot/share/gc/serial/serialFullGC.cpp @@ -512,6 +512,7 @@ void SerialFullGC::phase1_mark(bool clear_all_softrefs) { // This is the point where the entire marking should have completed. assert(_marking_stack.is_empty(), "Marking should have completed"); + CodeCache::on_gc_marking_cycle_finish(); { GCTraceTime(Debug, gc, phases) tm_m("Weak Processing", gc_timer()); diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 3de562e886d..eb2bed109b5 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -589,7 +589,6 @@ void SerialHeap::do_full_collection(bool clear_all_soft_refs) { gc_timer->register_gc_end(); gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions()); - CodeCache::on_gc_marking_cycle_finish(); CodeCache::arm_all_nmethods(); COMPILER2_PRESENT(DerivedPointerTable::update_pointers()); diff --git a/test/hotspot/jtreg/gc/TestCodeCacheUnload.java b/test/hotspot/jtreg/gc/TestCodeCacheUnload.java new file mode 100644 index 00000000000..03c857dba1e --- /dev/null +++ b/test/hotspot/jtreg/gc/TestCodeCacheUnload.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026, 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 gc; + +/* + * @test id=serial + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Serial + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseSerialGC gc.TestCodeCacheUnload + */ + +/* + * @test id=parallel + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Parallel + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseParallelGC gc.TestCodeCacheUnload + */ + +/* + * @test id=g1 + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.G1 + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseG1GC gc.TestCodeCacheUnload + */ + +/* + * @test id=shenandoah + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Shenandoah + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseShenandoahGC gc.TestCodeCacheUnload + */ + +/* + * @test id=z + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Z + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseZGC gc.TestCodeCacheUnload + */ + +import java.lang.reflect.Method; + +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + +public class TestCodeCacheUnload { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + public static class Target { + public static int test(int value) { + return value + 1; + } + } + + private static void compileAndMakeNotEntrant() throws Exception { + Method method = Target.class.getDeclaredMethod("test", int.class); + + method.invoke(null, 1); + if (!WB.enqueueMethodForCompilation(method, 1 /* compLevel */)) { + throw new AssertionError("Failed to enqueue target for compilation"); + } + while (WB.isMethodQueuedForCompilation(method)) { + Thread.sleep(50); + } + if (!WB.isMethodCompiled(method)) { + throw new AssertionError("Target is not compiled"); + } + + int deoptimized = WB.deoptimizeMethod(method); + if (deoptimized == 0) { + throw new AssertionError("No target nmethod was made not-entrant"); + } + } + + private static int countNotEntrantEntries() { + OutputAnalyzer output = new JMXExecutor().execute("Compiler.codelist"); + String target = "gc.TestCodeCacheUnload$Target.test"; + int result = 0; + + for (String line : output.asLines()) { + if (!line.contains(target)) { + continue; + } + + System.out.println("Found codelist entry: " + line); + String[] parts = line.trim().split("\\s+"); + int codeState = Integer.parseInt(parts[2]); + if (codeState == 1 /* not_entrant */) { + result++; + } + } + + return result; + } + + public static void main(String[] args) throws Exception { + compileAndMakeNotEntrant(); + WB.fullGC(); + + int notEntrantEntries = countNotEntrantEntries(); + System.out.println("Target not-entrant entries after 1 full GC: " + notEntrantEntries); + if (notEntrantEntries != 0) { + throw new AssertionError("Expected one full GC to unload the not-entrant nmethod"); + } + } +} diff --git a/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java new file mode 100644 index 00000000000..0c2d473b021 --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2026, 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 gc.g1; + +/* + * @test TestCodeCacheUnloadDuringConcurrentMark + * @summary Tests that G1 concurrent marking unloads a freshly not-entrant nmethod. + * @requires vm.gc.G1 + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.ClassUnloadingWithConcurrentMark != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseG1GC + * -XX:+ClassUnloadingWithConcurrentMark + * gc.g1.TestCodeCacheUnloadDuringConcurrentMark + */ + +import java.lang.reflect.Method; + +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.whitebox.WhiteBox; + +public class TestCodeCacheUnloadDuringConcurrentMark { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + static class Target { + public static int test(int value) { + return value + 1; + } + } + + private static void compileAndMakeNotEntrant(Method method) throws Exception { + Target.test(1); + if (!WB.enqueueMethodForCompilation(method, 1 /* compLevel */)) { + throw new AssertionError("Failed to enqueue target for compilation"); + } + while (WB.isMethodQueuedForCompilation(method)) { + Thread.sleep(50); + } + if (!WB.isMethodCompiled(method)) { + throw new AssertionError("Target is not compiled"); + } + + int deoptimized = WB.deoptimizeMethod(method); + if (deoptimized == 0) { + throw new AssertionError("No target nmethod was made not-entrant"); + } + } + + private static int countNotEntrantEntries() { + String target = TestCodeCacheUnloadDuringConcurrentMark.class.getName() + "$Target.test"; + int result = 0; + + for (String line : new JMXExecutor().execute("Compiler.codelist", true).asLines()) { + if (!line.contains(target)) { + continue; + } + + System.out.println("Found codelist entry: " + line); + String[] parts = line.trim().split("\\s+"); + int codeState = Integer.parseInt(parts[2]); + if (codeState == 1 /* not_entrant */) { + result++; + } + } + + return result; + } + + public static void main(String[] args) throws Exception { + compileAndMakeNotEntrant(Target.class.getDeclaredMethod("test", int.class)); + + int notEntrantEntries = countNotEntrantEntries(); + System.out.println("Target not-entrant entries before concurrent mark: " + notEntrantEntries); + if (notEntrantEntries == 0) { + throw new AssertionError("Expected a not-entrant target nmethod before concurrent mark"); + } + + WB.g1RunConcurrentGC(); + + notEntrantEntries = countNotEntrantEntries(); + System.out.println("Target not-entrant entries after concurrent mark: " + notEntrantEntries); + if (notEntrantEntries != 0) { + throw new AssertionError("Expected concurrent mark to unload the not-entrant target nmethod"); + } + } +}