diff --git a/src/hotspot/share/gc/g1/g1CollectionSet.cpp b/src/hotspot/share/gc/g1/g1CollectionSet.cpp index 2e10412cebf..6616a2308db 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSet.cpp +++ b/src/hotspot/share/gc/g1/g1CollectionSet.cpp @@ -385,6 +385,16 @@ static void print_finish_message(const char* reason, bool from_marking) { from_marking ? "marking" : "retained", reason); } +void G1CollectionSet::add_optional_group(G1CSetCandidateGroup* group, + uint& num_optional_regions, + double& predicted_optional_time_ms, + double predicted_time_ms) { + _optional_groups.append(group); + prepare_optional_group(group, num_optional_regions); + num_optional_regions += group->length(); + predicted_optional_time_ms += predicted_time_ms; +} + double G1CollectionSet::select_candidates_from_marking(double time_remaining_ms) { uint num_expensive_regions = 0; uint num_inital_regions = 0; @@ -404,6 +414,8 @@ double G1CollectionSet::select_candidates_from_marking(double time_remaining_ms) G1CSetCandidateGroupList* from_marking_groups = &candidates()->from_marking_groups(); + bool make_first_group_optional = G1ForceOptionalEvacuation; + log_debug(gc, ergo, cset)("Start adding marking candidates to collection set. " "Min %u regions, max %u regions, available %u regions (%u groups), " "time remaining %1.2fms, optional threshold %1.2fms", @@ -421,6 +433,15 @@ double G1CollectionSet::select_candidates_from_marking(double time_remaining_ms) double predicted_time_ms = group->predict_group_total_time_ms(); + if (make_first_group_optional) { + make_first_group_optional = false; + add_optional_group(group, + num_optional_regions, + predicted_optional_time_ms, + predicted_time_ms); + continue; + } + time_remaining_ms = MAX2(time_remaining_ms - predicted_time_ms, 0.0); // Add regions to old set until we reach the minimum amount if (num_inital_regions < min_old_cset_length) { @@ -456,10 +477,10 @@ double G1CollectionSet::select_candidates_from_marking(double time_remaining_ms) } else if (time_remaining_ms > 0) { // Keep adding optional regions until time is up. - _optional_groups.append(group); - prepare_optional_group(group, num_optional_regions); - num_optional_regions += group->length(); - predicted_optional_time_ms += predicted_time_ms; + add_optional_group(group, + num_optional_regions, + predicted_optional_time_ms, + predicted_time_ms); } else { print_finish_message("Predicted time too high", true); break; @@ -560,10 +581,10 @@ void G1CollectionSet::select_candidates_from_retained(double time_remaining_ms) num_initial_regions += group->length(); } else if (predicted_time_ms <= optional_time_remaining_ms) { // Prepare optional collection region. - _optional_groups.append(group); - prepare_optional_group(group, num_optional_regions); - num_optional_regions += group->length(); - predicted_optional_time_ms += predicted_time_ms; + add_optional_group(group, + num_optional_regions, + predicted_optional_time_ms, + predicted_time_ms); } else { // Fits neither initial nor optional time limit. Exit. break; @@ -645,6 +666,7 @@ uint G1CollectionSet::select_optional_groups(double time_remaining_ms) { log_debug(gc, ergo, cset)("Prepared %u regions out of %u for optional evacuation. Total predicted time: %.3fms", num_regions_selected, optional_regions_count, total_prediction_ms); + return num_regions_selected; } diff --git a/src/hotspot/share/gc/g1/g1CollectionSet.hpp b/src/hotspot/share/gc/g1/g1CollectionSet.hpp index 4cdfd4b93dd..db1824e44cb 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSet.hpp +++ b/src/hotspot/share/gc/g1/g1CollectionSet.hpp @@ -220,6 +220,13 @@ class G1CollectionSet { size_t offset, size_t length, uint worker_id) const; + + // Adds the given group to the optional groups list (_optional_groups) + // and updates all related bookkeeping. + void add_optional_group(G1CSetCandidateGroup* group, + uint& num_optional_regions, + double& predicted_optional_time_ms, + double predicted_time_ms); public: G1CollectionSet(G1CollectedHeap* g1h, G1Policy* policy); ~G1CollectionSet(); diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp index f73c4099ce6..e8caca490a9 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp @@ -815,13 +815,18 @@ void G1YoungCollector::evacuate_next_optional_regions(G1ParScanThreadStateSet* p void G1YoungCollector::evacuate_optional_collection_set(G1ParScanThreadStateSet* per_thread_states) { const double pause_start_time_ms = policy()->cur_pause_start_sec() * 1000.0; + double target_pause_time_ms = MaxGCPauseMillis; + + if (G1ForceOptionalEvacuation) { + target_pause_time_ms = DBL_MAX; + } while (!evacuation_alloc_failed() && collection_set()->num_optional_regions() > 0) { double time_used_ms = os::elapsedTime() * 1000.0 - pause_start_time_ms; - double time_left_ms = MaxGCPauseMillis - time_used_ms; + double time_left_ms = target_pause_time_ms - time_used_ms; - if (time_left_ms < 0 || + if (time_left_ms <= 0 || !collection_set()->finalize_optional_for_evacuation(time_left_ms * policy()->optional_evacuation_fraction())) { log_trace(gc, ergo, cset)("Skipping evacuation of %u optional regions, no more regions can be evacuated in %.3fms", collection_set()->num_optional_regions(), time_left_ms); diff --git a/src/hotspot/share/gc/g1/g1_globals.hpp b/src/hotspot/share/gc/g1/g1_globals.hpp index 0a054c6efd4..1c712492f74 100644 --- a/src/hotspot/share/gc/g1/g1_globals.hpp +++ b/src/hotspot/share/gc/g1/g1_globals.hpp @@ -370,6 +370,12 @@ "scan cost related prediction samples. A sample must involve " \ "the same or more than this number of code roots to be used.") \ \ + develop(bool, G1ForceOptionalEvacuation, false, \ + "Force optional evacuation for all GCs where there are old gen " \ + "collection set candidates." \ + "Also schedule all available optional groups for evacuation " \ + "regardless of timing.") \ + \ GC_G1_EVACUATION_FAILURE_FLAGS(develop, \ develop_pd, \ product, \ diff --git a/test/hotspot/jtreg/gc/g1/TestOptionalRegionGC.java b/test/hotspot/jtreg/gc/g1/TestOptionalRegionGC.java new file mode 100644 index 00000000000..3a5bb17fffc --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestOptionalRegionGC.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025, 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. + */ + +/* @test + * @bug 8352969 + * @summary Tests optional evacuation. + * @requires vm.gc.G1 + * @requires vm.debug + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.g1.TestOptionalRegionGC + */ + +package gc.g1; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TestOptionalRegionGC { + + private static OutputAnalyzer run() throws Exception { + return ProcessTools.executeLimitedTestJava( + "-XX:+WhiteBoxAPI", + "-Xbootclasspath/a:.", + "-Xmx300M", + "-Xms300M", + "-XX:G1HeapRegionSize=1M", + "-XX:+UseG1GC", + "-XX:MaxTenuringThreshold=1", + "-Xlog:gc+ergo+cset=trace", + "-XX:+G1ForceOptionalEvacuation", + "-XX:+VerifyAfterGC", + TestOptionalRegionGC.Action.class.getName()); + } + + public static void main(String args[]) throws Exception { + OutputAnalyzer out = run(); + out.shouldHaveExitValue(0); + Pattern pattern = Pattern.compile("Prepared (\\d+) regions out of (\\d+) for optional evacuation"); + Matcher matcher = pattern.matcher(out.getOutput()); + Asserts.assertTrue(matcher.find()); + String selectedNum = matcher.group(1); + String totalNum = matcher.group(2); + Asserts.assertTrue(Objects.equals(selectedNum, totalNum), "Error info: " + selectedNum + ", " + totalNum); + } + + public static class Action { + private static final WhiteBox wb = WhiteBox.getWhiteBox(); + private static final int MIN_OBJECT_SIZE = 64 * 1024; + private static final int MAX_OBJECT_SIZE = 120 * 1024; + private static final int NUM_OBJECTS = 1200; + + public static void main(String [] args) throws Exception { + // Remove garbage from VM initialization. + wb.fullGC(); + Random rand = new Random(42); + List objectList = new ArrayList<>(); + for (int i = 0; i < NUM_OBJECTS; i++) { + int objSize = MIN_OBJECT_SIZE + rand.nextInt(MAX_OBJECT_SIZE - MIN_OBJECT_SIZE); + byte[] obj = new byte[objSize]; + objectList.add(obj); + } + // Young GC promotes some objects to the old generation. + wb.youngGC(); + // Clear certain references for mixed GC. + for (int i = 0; i < NUM_OBJECTS; i+=2) { + objectList.set(i, null); + } + wb.g1RunConcurrentGC(); + // Perform the "Prepare Mixed" GC. + wb.youngGC(); + // Perform the "Mixed" GC. + wb.youngGC(); + } + } +}