diff --git a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp index 13890c026b2d..c11d0d1ccd9e 100644 --- a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp +++ b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp @@ -50,6 +50,9 @@ class G1OldGenAllocationTracker : public CHeapObj { public: G1OldGenAllocationTracker(); + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + size_t allocated_humongous_bytes_since_last_gc() const { return _allocated_humongous_bytes_since_last_gc; } + void add_allocated_bytes_since_last_gc(size_t bytes) { _allocated_bytes_since_last_gc += bytes; } void add_allocated_humongous_bytes_since_last_gc(size_t bytes) { _allocated_humongous_bytes_since_last_gc += bytes; } diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 70b7d796cd5e..d7bda9641e78 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -73,6 +73,8 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) : _rs_length(0), _pending_cards_at_gc_start(0), _concurrent_start_to_mixed(), + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + _max_hum_bytes_in_mixed_phase(0), _collection_set(nullptr), _g1h(nullptr), _phase_times_timer(gc_timer), @@ -106,6 +108,9 @@ void G1Policy::init(G1CollectedHeap* g1h, G1CollectionSet* collection_set) { // We immediately start allocating regions placing them in the collection set. // Initialize the collection set info. _collection_set->start_incremental_building(); + + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + _max_hum_bytes_in_mixed_phase = _g1h->reserved().byte_size() * G1MixedGCHumongousAllocThreshold / 100; } void G1Policy::record_young_gc_pause_start() { @@ -700,8 +705,29 @@ bool G1Policy::about_to_start_mixed_phase() const { return _g1h->concurrent_mark()->cm_thread()->in_progress() || collector_state()->in_young_gc_before_mixed(); } +// SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations +bool G1Policy::should_force_concurrent_ending_mixed_phase() { + bool policy_applies = + (_max_hum_bytes_in_mixed_phase > 0) && !_g1h->concurrent_mark()->cm_thread()->in_progress(); + + // The collector might not honor marking requests if expecting mixed collections. + // Force in case of excessive humongous allocations. + bool force_cm = policy_applies && + (collector_state()->in_young_gc_before_mixed() || !collector_state()->in_young_only_phase()) && + (old_gen_alloc_tracker()->allocated_humongous_bytes_since_last_gc() > _max_hum_bytes_in_mixed_phase); + + if (force_cm) { + log_info(gc, ergo, ihop)("Excessive humongous allocations in mixed phase: " SIZE_FORMAT "B since last gc threshold:" SIZE_FORMAT "B", + old_gen_alloc_tracker()->allocated_humongous_bytes_since_last_gc(), _max_hum_bytes_in_mixed_phase); + } + + return force_cm; +} + bool G1Policy::need_to_start_conc_mark(const char* source, size_t alloc_word_size) { - if (about_to_start_mixed_phase()) { + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + bool should_force_cm = _g1h->is_humongous(alloc_word_size) && should_force_concurrent_ending_mixed_phase(); + if (about_to_start_mixed_phase() && !should_force_cm) { return false; } @@ -714,8 +740,11 @@ bool G1Policy::need_to_start_conc_mark(const char* source, size_t alloc_word_siz bool result = false; if (marking_request_bytes > marking_initiating_used_threshold) { result = collector_state()->in_young_only_phase() && !collector_state()->in_young_gc_before_mixed(); - log_debug(gc, ergo, ihop)("%s occupancy: " SIZE_FORMAT "B allocation request: " SIZE_FORMAT "B threshold: " SIZE_FORMAT "B (%1.2f) source: %s", + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + result = result || should_force_cm; + log_debug(gc, ergo, ihop)("%s%s occupancy: " SIZE_FORMAT "B allocation request: " SIZE_FORMAT "B threshold: " SIZE_FORMAT "B (%1.2f) source: %s", result ? "Request concurrent cycle initiation (occupancy higher than threshold)" : "Do not request concurrent cycle initiation (still doing mixed collections)", + should_force_cm ? "(ending mixed phase)" : "", cur_used_bytes, alloc_byte_size, marking_initiating_used_threshold, (double) marking_initiating_used_threshold / _g1h->capacity() * 100, source); } return result; @@ -1213,7 +1242,9 @@ void G1Policy::decide_on_concurrent_start_pause() { } else if (_g1h->is_user_requested_concurrent_full_gc(cause) || (cause == GCCause::_codecache_GC_threshold) || (cause == GCCause::_codecache_GC_aggressive) || - (cause == GCCause::_wb_breakpoint)) { + (cause == GCCause::_wb_breakpoint) || + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + (cause == GCCause::_g1_humongous_allocation && should_force_concurrent_ending_mixed_phase())) { // Initiate a concurrent start. A concurrent start must be a young only // GC, so the collector state must be updated to reflect this. collector_state()->set_in_young_only_phase(true); @@ -1226,8 +1257,13 @@ void G1Policy::decide_on_concurrent_start_pause() { abandon_collection_set_candidates(); abort_time_to_mixed_tracking(); initiate_conc_mark(); - log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)", - (cause == GCCause::_wb_breakpoint) ? "run_to breakpoint" : "user"); + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + if (cause == GCCause::_g1_humongous_allocation) { + log_debug(gc, ergo)("Initiate concurrent cycle (%s)", GCCause::to_string(cause)); + } else { + log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)", + (cause == GCCause::_wb_breakpoint) ? "run_to breakpoint" : "user"); + } } else { // The concurrent marking thread is still finishing up the // previous cycle. If we start one right now the two cycles diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 238076bb1e67..e985dfaee19b 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -109,6 +109,11 @@ class G1Policy: public CHeapObj { G1ConcurrentStartToMixedTimeTracker _concurrent_start_to_mixed; + // SapMachine 2026-01-20 + // Threshold for humongous allocations since last gc for ending the current mixed phase + // and initiate a new marking cycle + size_t _max_hum_bytes_in_mixed_phase; + bool should_update_surv_rate_group_predictors() { return collector_state()->in_young_only_phase() && !collector_state()->mark_or_rebuild_in_progress(); } @@ -285,6 +290,9 @@ class G1Policy: public CHeapObj { // Indicate that we aborted marking before doing any mixed GCs. void abort_time_to_mixed_tracking(); + // SapMachine 2026-01-20: force G1 marking in mixed phase in case of excessive hum. allocations + bool should_force_concurrent_ending_mixed_phase(); + public: G1Policy(STWGCTimer* gc_timer); diff --git a/src/hotspot/share/gc/g1/g1_globals.hpp b/src/hotspot/share/gc/g1/g1_globals.hpp index 4a0433621f5c..33d0f193643b 100644 --- a/src/hotspot/share/gc/g1/g1_globals.hpp +++ b/src/hotspot/share/gc/g1/g1_globals.hpp @@ -323,6 +323,15 @@ "related prediction sample. That sample must involve the same or "\ "more than that number of cards to be used.") \ \ + /* SapMachine 2026-01-20 */ \ + product(uintx, G1MixedGCHumongousAllocThreshold, 0, EXPERIMENTAL, \ + "Threshold for humongous allocations since last gc to initiate a "\ + "new concurrent cycle even if the collector is not yet done with "\ + "mixed collections. This can help in longer phases where no " \ + "evacuation pauses are scheduled because most allocations are " \ + "humongous. The value is a percentage of the maximum heap size.") \ + range(0, 100) \ + \ GC_G1_EVACUATION_FAILURE_FLAGS(develop, \ develop_pd, \ product, \ diff --git a/test/hotspot/jtreg/gc/g1/TestHumongousAllocMixedPhase.java b/test/hotspot/jtreg/gc/g1/TestHumongousAllocMixedPhase.java new file mode 100644 index 000000000000..fff9566f4f8b --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestHumongousAllocMixedPhase.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2026 SAP SE. 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; + +import static gc.testlibrary.Allocation.blackHole; + +/* + * @test TestHumongousAllocMixedPhase + * @bug 8355972 + * @summary G1: should finish mixed phase and start a new concurrent + * cycle if there are excessive humongous allocations + * @requires vm.gc.G1 + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @library / + * @run driver gc.g1.TestHumongousAllocMixedPhase + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestHumongousAllocMixedPhase { + // Heap sizes < 224 MB are increased to 224 MB if vm_page_size == 64K to + // fulfill alignment constraints. + private static final int heapSize = 224; // MB + private static final int heapRegionSize = 1; // MB + + public static void main(String[] args) throws Exception { + boolean success = false; + int allocDelayMsMax = 500; + for (int allocDelayMs = 10; !success; allocDelayMs *= 4) { + System.out.println("Testing with allocDelayMs=" + allocDelayMs); + success = runTest(allocDelayMs, allocDelayMs >= allocDelayMsMax /* lastTry*/); + } + } + + static boolean runTest(int allocDelayMs, boolean lastTry) throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UseG1GC", + "-Xms" + heapSize + "m", + "-Xmx" + heapSize + "m", + "-XX:G1HeapRegionSize=" + heapRegionSize + "m", + "-Xlog:gc*", + "-XX:+UnlockExperimentalVMOptions", + "-XX:G1MixedGCHumongousAllocThreshold=30", + HumongousObjectAllocator.class.getName(), + Integer.toString(allocDelayMs)); + + boolean success = false; + String pauseFull = "Pause Full (G1 Compaction Pause)"; + if (lastTry) { + output.shouldNotContain(pauseFull); + output.shouldHaveExitValue(0); + success = true; + } else { + success = !output.stdoutContains(pauseFull) && !output.getStderr().contains(pauseFull) && + output.getExitValue() == 0; + } + return success; + } + + static class HumongousObjectAllocator { + public static void main(String[] args) throws Exception { + int allocDelayMs = Integer.parseInt(args[0]); + // Make sure there are some candidate regions for mixed pauses. + // (Eg w/o CDS there might be none) + System.gc(); + for (int i = 0; i < 100; i++) { + Integer[] humongous = new Integer[5_000_000]; + blackHole(humongous); + System.out.println(i); + Thread.sleep(allocDelayMs); + } + } + } +} +