From 802bd59e75964608e2ccdbe4c80ff1ffdb12e075 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 16 Feb 2026 15:18:03 +0100 Subject: [PATCH 1/3] 8380778 * initial version * rename _has_no_references() to _has_no_oops() * fix issue with large instanceoops * fix assertions? * initial version * rename _has_no_references() to _has_no_oops() * fix issue with large instanceoops * fix condition to make assert pass --- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 3 + .../share/gc/g1/g1CollectedHeap.inline.hpp | 8 ++ src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 4 + .../share/gc/g1/g1ConcurrentMark.inline.hpp | 18 ++- src/hotspot/share/gc/g1/g1FullCollector.cpp | 2 +- src/hotspot/share/gc/g1/g1YoungCollector.cpp | 33 +++--- .../gc/g1/g1YoungGCPostEvacuateTasks.cpp | 9 +- .../g1/TestEagerReclaimHumongousRegions.java | 112 +++++++++++------- 8 files changed, 121 insertions(+), 68 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 7a4cde9001e..db0bc10374a 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1225,6 +1225,9 @@ class G1CollectedHeap : public CollectedHeap { // requires. static size_t humongous_obj_size_in_regions(size_t word_size); + // Returns whether the given array has oops or not in its body. + inline static bool array_has_oops(oop obj); + // Returns how much space in bytes an allocation of word_size will use up in the // heap. static size_t allocation_used_bytes(size_t word_size); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp index 8782b65b6f9..7a8644654d0 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp @@ -42,6 +42,7 @@ #include "gc/shared/collectedHeap.inline.hpp" #include "gc/shared/markBitMap.inline.hpp" #include "gc/shared/taskqueue.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/stackChunkOop.hpp" #include "runtime/threadSMR.inline.hpp" #include "utilities/bitMap.inline.hpp" @@ -109,6 +110,13 @@ inline G1HeapRegion* G1CollectedHeap::region_at(uint index) const { return _hrm. // Return the region with the given index, or null if unmapped. It assumes the index is valid. inline G1HeapRegion* G1CollectedHeap::region_at_or_null(uint index) const { return _hrm.at_or_null(index); } +inline bool G1CollectedHeap::array_has_oops(oop obj) { + assert(obj->is_array(), "must be"); + // Just assume that any instanceOops always have references, i.e. they return "false" although + // they may or may not have references. + return obj->is_array_with_oops(); +} + template inline void G1CollectedHeap::humongous_obj_regions_iterate(G1HeapRegion* start, const Func& f) { assert(start->is_starts_humongous(), "must be"); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 8bd04437097..0ddafbed9e3 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -842,6 +842,10 @@ class G1CMTask : public TerminatorTerminator { // mark bitmap scan, and so needs to be pushed onto the mark stack. bool is_below_finger(oop obj, HeapWord* global_finger) const; + // Returns whether the given oop can be processed immediately, i.e. does not need + // to be pushed into the mark stack. + // Note that even with a true result, the klass may need to be inspected. + static bool can_be_processed_immediately(oop obj); template void process_grey_task_entry(G1TaskQueueEntry task_entry, bool stolen); static bool should_be_sliced(oop obj); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp index 29fe5d94aa3..e7a0ae3f3a7 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp @@ -162,7 +162,8 @@ inline bool G1CMTask::is_below_finger(oop obj, HeapWord* global_finger) const { template inline void G1CMTask::process_grey_task_entry(G1TaskQueueEntry task_entry, bool stolen) { - assert(scan || (!task_entry.is_partial_array_state() && task_entry.to_oop()->is_typeArray()), "Skipping scan of grey non-typeArray"); + assert(scan || (!task_entry.is_partial_array_state() && !G1CollectedHeap::array_has_oops(task_entry.to_oop())), + "Skipping scan of grey object that needs scanning"); assert(task_entry.is_partial_array_state() || _mark_bitmap->is_marked(cast_from_oop(task_entry.to_oop())), "Any stolen object should be a slice or marked"); @@ -177,10 +178,17 @@ inline void G1CMTask::process_grey_task_entry(G1TaskQueueEntry task_entry, bool _words_scanned += obj->oop_iterate_size(_cm_oop_closure); } } + } else if (!task_entry.to_oop()->is_typeArray()) { + // Need to process the klass except for the built-in type array. + _cm_oop_closure->do_klass(task_entry.to_oop()->klass()); } check_limits(); } +inline bool G1CMTask::can_be_processed_immediately(oop obj) { + return obj->is_array() && !G1CollectedHeap::array_has_oops(obj); +} + inline bool G1CMTask::should_be_sliced(oop obj) { return obj->is_array_with_oops() && ((objArrayOop)obj)->length() >= (int)ObjArrayMarkingStride; } @@ -274,8 +282,8 @@ inline bool G1CMTask::make_reference_grey(oop obj) { // correctness problems. if (is_below_finger(obj, global_finger)) { G1TaskQueueEntry entry(obj); - if (obj->is_typeArray()) { - // Immediately process arrays of primitive types, rather + if (can_be_processed_immediately(obj)) { + // Immediately process arrays of types without oops, rather // than pushing on the mark stack. This keeps us from // adding humongous objects to the mark stack that might // be reclaimed before the entry is processed - see @@ -283,8 +291,8 @@ inline bool G1CMTask::make_reference_grey(oop obj) { // objects. The cost of the additional type test is // mitigated by avoiding a trip through the mark stack, // by only doing a bookkeeping update and avoiding the - // actual scan of the object - a typeArray contains no - // references, and the metadata is built-in. + // actual scan of the object - the object contains no + // references (but the metadata must be processed). process_grey_task_entry(entry, false /* stolen */); } else { push(entry); diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index e8498250f85..90a9a0e4f45 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -24,7 +24,7 @@ #include "classfile/classLoaderDataGraph.hpp" #include "cppstdlib/new.hpp" -#include "gc/g1/g1CollectedHeap.hpp" +//#include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1FullCollector.inline.hpp" #include "gc/g1/g1FullGCAdjustTask.hpp" #include "gc/g1/g1FullGCCompactTask.hpp" diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp index a9db9a7c269..6e9db9c66b2 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp @@ -341,13 +341,14 @@ class G1PrepareEvacuationTask : public WorkerTask { // It also helps with G1 allocating humongous objects as old generation // objects although they might also die quite quickly. // - // TypeArray objects are allowed to be reclaimed even if allocated before + // Humongous objects without oops (typeArrays, flatArrays without oops in + // its elements) are allowed to be reclaimed even if allocated before // the start of concurrent mark. For this we rely on mark stack insertion - // to exclude is_typeArray() objects, preventing reclaiming an object - // that is in the mark stack. We also rely on the metadata for - // such objects to be built-in and so ensured to be kept live. + // to exclude them, preventing reclaiming an object + // that is in the mark stack. That code also ensures that metadata (klass) + // is kept live. // - // Non-typeArrays that were allocated before marking are excluded from + // Other humongous objects that were allocated before marking are excluded from // eager reclaim during marking. One issue is the problem described // above with scrubbing the mark stack, but there is also a problem // causing these humongous objects being collected incorrectly: @@ -362,16 +363,17 @@ class G1PrepareEvacuationTask : public WorkerTask { // garbage collection. o1 still has the reference to o2, but since o1 had // already been scanned we do not detect o2 to be still live and reclaim it. // - // There is another minor problem with non-typeArray regions being the source - // of remembered set entries in other region's remembered sets. There are - // two cases: first, the remembered set entry is in a Free region after reclaim. - // We handle this case by ignoring these cards during merging the remembered - // sets. + // There is another minor problem with these humongous objects with oops being + // the source of remembered set entries in other region's remembered sets. + // There are two cases: first, the remembered set entry is in a Free region + // after reclaim. We handle this case by ignoring these cards during merging + // the remembered sets. // - // Second, there may be cases where eagerly reclaimed regions were already - // reallocated. This may cause scanning of these outdated remembered set - // entries, containing some objects. But apart from extra work this does - // not cause correctness issues. + // Second, there may be cases where regions previously containing eagerly + // reclaimed objects were already allocated into again. + // This may cause scanning of these outdated remembered set entries, + // containing some objects. But apart from extra work this does not cause + // correctness issues. // There is no difference between scanning cards covering an effectively // dead humongous object vs. some other objects in reallocated regions. // @@ -388,7 +390,8 @@ class G1PrepareEvacuationTask : public WorkerTask { // // After the pause, having reclaimed h, obviously the mutator can't fetch // the reference from h any more. - if (!obj->is_typeArray()) { + bool potentially_has_oops = !obj->is_array() || _g1h->array_has_oops(obj); + if (potentially_has_oops) { // All regions that were allocated before marking have a TAMS != bottom. bool allocated_before_mark_start = region->bottom() != _g1h->concurrent_mark()->top_at_mark_start(region); bool mark_in_progress = _g1h->collector_state()->mark_in_progress(); diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp index a0013d27172..9a61d136c6f 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp @@ -396,10 +396,11 @@ class G1FreeHumongousRegionClosure : public G1HeapRegionIndexClosure { ResourceMark rm; bool allocated_after_mark_start = r->bottom() == _g1h->concurrent_mark()->top_at_mark_start(r); bool mark_in_progress = _g1h->collector_state()->mark_in_progress(); - guarantee(obj->is_typeArray() || (allocated_after_mark_start || !mark_in_progress), - "Only eagerly reclaiming primitive arrays is supported, other humongous objects only if allocated after mark start, but the object " - PTR_FORMAT " (%s) is not (mark %d allocated after mark: %d).", - p2i(r->bottom()), obj->klass()->name()->as_C_string(), mark_in_progress, allocated_after_mark_start); + bool potentially_has_oops = !obj->is_array() || G1CollectedHeap::array_has_oops(obj); + guarantee(!potentially_has_oops || (allocated_after_mark_start || !mark_in_progress), + "Only eagerly reclaiming arrays without oops is always supported, other humongous objects only if allocated after mark start, but the object " + PTR_FORMAT " (%s) is not (allocated after mark: %d mark in progress %d).", + p2i(r->bottom()), obj->klass()->name()->as_C_string(), allocated_after_mark_start, mark_in_progress); } log_debug(gc, humongous)("Reclaimed humongous region %u (object size %zu @ " PTR_FORMAT ")", region_index, diff --git a/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java index fd1924b9644..65ccdf285ea 100644 --- a/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java +++ b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -32,9 +32,10 @@ * @library /test/lib /testlibrary / * @modules java.base/jdk.internal.misc * java.management + * @enablePreview * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI gc.g1.TestEagerReclaimHumongousRegions + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI --enable-preview gc.g1.TestEagerReclaimHumongousRegions */ import java.util.ArrayList; @@ -51,7 +52,7 @@ public class TestEagerReclaimHumongousRegions { private static final WhiteBox WB = WhiteBox.getWhiteBox(); - enum ObjectType { TYPE_ARRAY, OBJ_ARRAY } + enum ObjectType { TYPE_ARRAY, OBJ_ARRAY, FLAT_TYPE_ARRAY, FLAT_OBJ_ARRAY } enum ReferencePolicy { KEEP, DROP } enum AllocationTiming { BEFORE_MARK_START, AFTER_MARK_START} @@ -86,21 +87,22 @@ enum ExpectedState { */ private static String runHelperVM(ObjectType type, ReferencePolicy refPolicy, AllocationTiming timing, String phase) throws Exception { - boolean useTypeArray = (type == ObjectType.TYPE_ARRAY); + int arrayKind = type.ordinal(); boolean keepReference = (refPolicy == ReferencePolicy.KEEP); boolean allocateAfter = (timing == AllocationTiming.AFTER_MARK_START); OutputAnalyzer output = ProcessTools.executeLimitedTestJava("-XX:+UseG1GC", "-Xmx20M", - "-Xms20m", + "-Xms20M", "-XX:+UnlockDiagnosticVMOptions", "-XX:+VerifyAfterGC", "-Xbootclasspath/a:.", "-Xlog:gc=debug,gc+humongous=debug", "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "--enable-preview", TestEagerReclaimHumongousRegionsClearMarkBitsRunner.class.getName(), - String.valueOf(useTypeArray), + String.valueOf(arrayKind), String.valueOf(keepReference), String.valueOf(allocateAfter), phase); @@ -152,53 +154,77 @@ private static void runTest(ObjectType type, Asserts.assertTrue(expected.reclaimed == reclaimed, "Wrong log output reclaiming humongous region"); } - public static void main(String[] args) throws Exception { - System.out.println("Tests checking eager reclaim for when the object is allocated before mark start."); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + private static void runTestNoRefsFor(ObjectType ot) throws Exception { + System.out.println("Tests checking eager reclaim for when the object of type " + ot + " is allocated before mark start."); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + + System.out.println("Tests checking eager reclaim for when the object of type " + ot + " is allocated after mark start."); + // These must not be marked (as they were allocated after mark start), and they are always candidates. Reclamation depends on whether there is a reference. + runTest(ot, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + } - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_NOTCANDIDATE_NOTRECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + private static void runTestWithRefsFor(ObjectType ot) throws Exception { + System.out.println("Tests checking eager reclaim for when the object of type " + ot + " is allocated before mark start."); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_NOTCANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_NOTCANDIDATE_NOTRECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_NOTCANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); - System.out.println("Tests checking eager reclaim for when the object is allocated after mark start."); + System.out.println("Tests checking eager reclaim for when the object of type " + ot + " is allocated after mark start."); // These must not be marked (as they were allocated after mark start), and they are always candidates. Reclamation depends on whether there is a reference. - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + runTest(ot, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + } - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); - runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED); + public static void main(String[] args) throws Exception { + runTestNoRefsFor(ObjectType.TYPE_ARRAY); + runTestWithRefsFor(ObjectType.OBJ_ARRAY); + runTestNoRefsFor(ObjectType.FLAT_TYPE_ARRAY); + runTestWithRefsFor(ObjectType.FLAT_OBJ_ARRAY); } } class TestEagerReclaimHumongousRegionsClearMarkBitsRunner { + value class RefValue { + Object o; + RefValue() { o = null; } + } + + value class IntValue { + int i; + IntValue() { i = 0; } + } + private static final WhiteBox WB = WhiteBox.getWhiteBox(); private static final int SIZE = 1024 * 1024; - private static Object allocateHumongousObj(boolean useTypeArray) { - if (useTypeArray) { - return new int[SIZE]; - } else { - return new Object[SIZE]; + private static Object allocateHumongousObj(int type) { + switch (type) { + case 0: return new int[SIZE]; + case 1: return new Object[SIZE]; + case 2: return new IntValue[SIZE]; + case 3: return new RefValue[SIZE]; + default: throw new RuntimeException("Unknown object type " + type); } } @@ -206,17 +232,17 @@ public static void main(String[] args) throws Exception { if (args.length != 4) { throw new Exception("Invalid number of arguments " + args.length); } - boolean useTypeArray = Boolean.parseBoolean(args[0]); + int arrayType = Integer.parseInt(args[0]); boolean keepReference = Boolean.parseBoolean(args[1]); boolean allocateAfter = Boolean.parseBoolean(args[2]); String phase = args[3]; - System.out.println("useTypeArray: " + useTypeArray + " keepReference: " + keepReference + " allocateAfter " + allocateAfter + " phase: " + phase); + System.out.println("arrayType: " + arrayType + " keepReference: " + keepReference + " allocateAfter " + allocateAfter + " phase: " + phase); WB.fullGC(); Object largeObj = null; // Allocated humongous object. if (!allocateAfter) { - largeObj = allocateHumongousObj(useTypeArray); + largeObj = allocateHumongousObj(arrayType); } WB.concurrentGCAcquireControl(); @@ -225,7 +251,7 @@ public static void main(String[] args) throws Exception { System.out.println("Phase " + phase + " reached"); if (allocateAfter) { - largeObj = allocateHumongousObj(useTypeArray); + largeObj = allocateHumongousObj(arrayType); } if (!keepReference) { From 773945b5d63637f921b144fb8f8ccdafb5b7aad5 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Thu, 26 Mar 2026 11:17:56 +0100 Subject: [PATCH 2/3] * fix botched merge --- .../g1/TestEagerReclaimHumongousRegions.java | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java index 6824e2e3eaf..a0f9af91df7 100644 --- a/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java +++ b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousRegions.java @@ -102,7 +102,7 @@ private static String runHelperVM(List args, ObjectType type, ReferenceP "-XX:+WhiteBoxAPI", "--enable-preview", TestEagerReclaimHumongousRegionsClearMarkBitsRunner.class.getName(), - String.valueOf(useTypeArray), + String.valueOf(arrayKind), String.valueOf(keepReference), String.valueOf(allocateAfter), phase); @@ -160,13 +160,6 @@ private static void verifyLog(String log, AllocationTiming timing, ExpectedState Asserts.assertTrue(expected.reclaimed == reclaimed, "Wrong log output reclaiming humongous region"); } -<<<<<<< HEAD - private static void runTestNoRefsFor(ObjectType ot) throws Exception { - System.out.println("Tests checking eager reclaim for when the object of type " + ot + " is allocated before mark start."); - runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); -======= private static void runTest(ObjectType type, ReferencePolicy refPolicy, AllocationTiming timing, @@ -184,12 +177,11 @@ private static void runTest(ObjectType type, verifyLog(jfrLog, timing, expected); } - public static void main(String[] args) throws Exception { - System.out.println("Tests checking eager reclaim for when the object is allocated before mark start."); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); - runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); ->>>>>>> lworld + private static void runTestNoRefsFor(ObjectType ot) throws Exception { + System.out.println("Tests checking eager reclaim for when the object of type " + ot + " is allocated before mark start."); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED); + runTest(ot, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED); runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); runTest(ot, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED); From 43454add137f80cf01748c649b5d0865f6ba2502 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 30 Mar 2026 11:58:43 +0200 Subject: [PATCH 3/3] * walulyai review: remove G1CollectedHeap::array_with_oops() --- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 3 --- src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp | 7 ------- src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp | 4 ++-- src/hotspot/share/gc/g1/g1YoungCollector.cpp | 2 +- src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp | 6 +++--- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 22968f6d3aa..b5cb9167d92 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1224,9 +1224,6 @@ class G1CollectedHeap : public CollectedHeap { // requires. static size_t humongous_obj_size_in_regions(size_t word_size); - // Returns whether the given array has oops or not in its body. - inline static bool array_has_oops(oop obj); - // Returns how much space in bytes an allocation of word_size will use up in the // heap. static size_t allocation_used_bytes(size_t word_size); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp index 20abc9b16fc..a8019203f1e 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp @@ -110,13 +110,6 @@ inline G1HeapRegion* G1CollectedHeap::region_at(uint index) const { return _hrm. // Return the region with the given index, or null if unmapped. It assumes the index is valid. inline G1HeapRegion* G1CollectedHeap::region_at_or_null(uint index) const { return _hrm.at_or_null(index); } -inline bool G1CollectedHeap::array_has_oops(oop obj) { - assert(obj->is_array(), "must be"); - // Just assume that any instanceOops always have references, i.e. they return "false" although - // they may or may not have references. - return obj->is_array_with_oops(); -} - template inline void G1CollectedHeap::humongous_obj_regions_iterate(G1HeapRegion* start, const Func& f) { assert(start->is_starts_humongous(), "must be"); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp index e7a0ae3f3a7..fdd0df92afe 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp @@ -162,7 +162,7 @@ inline bool G1CMTask::is_below_finger(oop obj, HeapWord* global_finger) const { template inline void G1CMTask::process_grey_task_entry(G1TaskQueueEntry task_entry, bool stolen) { - assert(scan || (!task_entry.is_partial_array_state() && !G1CollectedHeap::array_has_oops(task_entry.to_oop())), + assert(scan || (!task_entry.is_partial_array_state() && !task_entry.to_oop()->is_array_with_oops()), "Skipping scan of grey object that needs scanning"); assert(task_entry.is_partial_array_state() || _mark_bitmap->is_marked(cast_from_oop(task_entry.to_oop())), "Any stolen object should be a slice or marked"); @@ -186,7 +186,7 @@ inline void G1CMTask::process_grey_task_entry(G1TaskQueueEntry task_entry, bool } inline bool G1CMTask::can_be_processed_immediately(oop obj) { - return obj->is_array() && !G1CollectedHeap::array_has_oops(obj); + return obj->is_array() && !obj->is_array_with_oops(); } inline bool G1CMTask::should_be_sliced(oop obj) { diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp index 84ce69da001..e9ea877e9ee 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp @@ -390,7 +390,7 @@ class G1PrepareEvacuationTask : public WorkerTask { // // After the pause, having reclaimed h, obviously the mutator can't fetch // the reference from h any more. - bool potentially_has_oops = !obj->is_array() || _g1h->array_has_oops(obj); + bool potentially_has_oops = !obj->is_array() || obj->is_array_with_oops(); if (potentially_has_oops) { // All regions that were allocated before marking have a TAMS != bottom. bool allocated_before_mark_start = region->bottom() != _g1h->concurrent_mark()->top_at_mark_start(region); diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp index e556210dbf2..f8950698059 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp @@ -396,11 +396,11 @@ class G1FreeHumongousRegionClosure : public G1HeapRegionIndexClosure { ResourceMark rm; bool allocated_after_mark_start = r->bottom() == _g1h->concurrent_mark()->top_at_mark_start(r); bool mark_in_progress = _g1h->collector_state()->is_in_marking(); - bool potentially_has_oops = !obj->is_array() || G1CollectedHeap::array_has_oops(obj); + bool potentially_has_oops = !obj->is_array() || obj->is_array_with_oops(); guarantee(!potentially_has_oops || (allocated_after_mark_start || !mark_in_progress), "Only eagerly reclaiming arrays without oops is always supported, other humongous objects only if allocated after mark start, but the object " - PTR_FORMAT " (%s) is not (allocated after mark: %d mark in progress %d).", - p2i(r->bottom()), obj->klass()->name()->as_C_string(), allocated_after_mark_start, mark_in_progress); + PTR_FORMAT " (%s) is not (allocated after mark: %d mark in progress %d potentially has oops %d is_array %d array_with_oops %d).", + p2i(r->bottom()), obj->klass()->name()->as_C_string(), allocated_after_mark_start, mark_in_progress, potentially_has_oops, obj->is_array(), obj->is_array_with_oops()); } log_debug(gc, humongous)("Reclaimed humongous region %u (object size %zu @ " PTR_FORMAT ")", region_index,