Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class G1OldGenAllocationTracker : public CHeapObj<mtGC> {
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; }

Expand Down
46 changes: 41 additions & 5 deletions src/hotspot/share/gc/g1/g1Policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/hotspot/share/gc/g1/g1Policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ class G1Policy: public CHeapObj<mtGC> {

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();
}
Expand Down Expand Up @@ -285,6 +290,9 @@ class G1Policy: public CHeapObj<mtGC> {
// 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);
Expand Down
9 changes: 9 additions & 0 deletions src/hotspot/share/gc/g1/g1_globals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down
98 changes: 98 additions & 0 deletions test/hotspot/jtreg/gc/g1/TestHumongousAllocMixedPhase.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}

Loading