8387742: Reclaim CodeCache nmethods more promptly

Reviewed-by: tschatzl, shade
This commit is contained in:
Albert Mingkun Yang 2026-07-10 06:36:00 +00:00
parent 3354ad3b6f
commit 3e2366e7f5
9 changed files with 302 additions and 10 deletions

View File

@ -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();
}

View File

@ -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; }

View File

@ -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);

View File

@ -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++) {

View File

@ -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);

View File

@ -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());

View File

@ -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());

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}