8382085: [shenandoah] Cannot transfer regions that are affiliated - assertion

Reviewed-by: wkemper, ruili, xpeng
This commit is contained in:
Kelvin Nilsen 2026-07-23 15:25:58 +00:00
parent 94cd8b6900
commit 2c9a99a931
4 changed files with 245 additions and 6 deletions

View File

@ -1,6 +1,6 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 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
@ -157,7 +157,9 @@ void ShenandoahGenerationalFullGC::compute_balances() {
// Invoke this in case we are able to transfer memory from OLD to YOUNG
size_t allocation_runway =
heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0L);
heap->compute_old_generation_balance(allocation_runway, 0, 0);
size_t max_transfer = MIN2(allocation_runway,
heap->young_generation()->free_unaffiliated_regions() * ShenandoahHeapRegion::region_size_bytes());
heap->compute_old_generation_balance(max_transfer, 0, 0);
}
ShenandoahPrepareForGenerationalCompactionObjectClosure::ShenandoahPrepareForGenerationalCompactionObjectClosure(PreservedMarks* preserved_marks,

View File

@ -448,7 +448,9 @@ jint ShenandoahHeap::initialize() {
// gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_cset_regions)
// until after the heap is fully initialized. So we make up a safe value here.
size_t allocation_runway = InitialHeapSize / 2;
gen_heap->compute_old_generation_balance(allocation_runway, old_trashed_regions, young_trashed_regions);
// We're initializing the heap. All regions within young are initially empty.
size_t max_transfer = allocation_runway;
gen_heap->compute_old_generation_balance(max_transfer, old_trashed_regions, young_trashed_regions);
}
_free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old);
}
@ -2643,7 +2645,10 @@ void ShenandoahHeap::rebuild_free_set_within_phase() {
ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap();
size_t allocation_runway =
gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_trashed_regions);
gen_heap->compute_old_generation_balance(allocation_runway, old_trashed_regions, young_trashed_regions);
size_t max_transfer = MIN2(allocation_runway,
(gen_heap->young_generation()->free_unaffiliated_regions() + young_trashed_regions) *
ShenandoahHeapRegion::region_size_bytes());
gen_heap->compute_old_generation_balance(max_transfer, old_trashed_regions, young_trashed_regions);
}
// Rebuild free set based on adjusted generation sizes.
_free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, old_region_count);

View File

@ -1,7 +1,7 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 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
@ -491,7 +491,10 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent
ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap();
size_t allocation_runway =
gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_trash_regions);
gen_heap->compute_old_generation_balance(allocation_runway, old_trash_regions, young_trash_regions);
size_t max_transfer = MIN2(allocation_runway,
(gen_heap->young_generation()->free_unaffiliated_regions() + young_trash_regions) *
ShenandoahHeapRegion::region_size_bytes());
gen_heap->compute_old_generation_balance(max_transfer, old_trash_regions, young_trash_regions);
heap->free_set()->finish_rebuild(young_trash_regions, old_trash_regions, num_old);
}
}

View File

@ -0,0 +1,229 @@
/*
* Copyright Amazon.com Inc. 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.shenandoah.generational;
/*
* @test id=generational
* @summary Test that we do not attempt to transfer to the old
* generation regions that are affiliated with young
* @bug 8382085
* @key stress
* @requires vm.gc.Shenandoah
* @requires vm.flagless
* @requires os.maxMemory >= 2g
* @library /test/lib
*
* @run main/othervm/timeout=960 -Xms1g -Xmx1g
* -XX:+UnlockExperimentalVMOptions
* -XX:ShenandoahRegionSize=512K
* -XX:+AlwaysPreTouch
* -XX:+UseShenandoahGC
* -XX:ShenandoahGCMode=generational
* -XX:ShenandoahMinFreeThreshold=5
* -XX:ShenandoahGuaranteedYoungGCInterval=0
* -XX:ShenandoahGuaranteedOldGCInterval=0
* -XX:ShenandoahOldEvacPercent=95
* -XX:ShenandoahPromoEvacWaste=3.0
* gc.shenandoah.generational.TestTransferOfAffiliated
*/
import java.util.Random;
import jdk.test.lib.Asserts;
public class TestTransferOfAffiliated {
// Heap size is 1 GB. HeapRegionSize is 512KB of memory.
// Note: 512KB/region * 2048 regions = 1 GB.
//
// Size calculations below ignore the overhead of array headers,
// except to acknowledge that array header causes that only 1
// inner array fits per heap region. Size calculations also
// assume the rootArray is negligible.
private static Integer[][] rootArray;
// Each inner array spans 256K of memory plus a small number of
// bytes for the array header. Only 1 inner array fits within each
// HeapRegion, causing a large amount of fragmentation. The number
// of elements in an array is 256K divided by 4 bytes per
// (compressed) oop
private static final int INNER_ARRAY_SLOTS = (256 * 1024) / 4;
// Integer objects are referenced from the inner array. We want
// each InnerArray to consume a full HeapRegion after we account
// for the InnerIntegers referenced from the array. We cannot fill
// the entire array as that would consume more than a HeapRegion's
// worth of memory. We assign Integer objects to random elements
// of the inner array. In the case that two Integer objects are
// randomly assigned to the same array element, one of the two
// will immediately become garbage. The expectation is that the
// rare collision on array slots is sufficient to allow the
// "Inner Array", including its array header and all of its
// Integer elements to pack within a single heap region.
//
// Assume each Integer object consists of 4 bytes for int value,
// plus 8 bytes for compressed Lilliput 1 header, plus 4 bytes for
// alignment. Alternatively, if we don't use Lilliput 1, each
// Integer consumes the same 16 bytes: 12 bytes for non-compact
// object header plus 4 bytes for int value.
//
// The number of InnerIntegers for each InnerArray is 256K (half
// the region size) / 16 bytes / Integer
private static final int INNER_INTEGERS = (256 * 1024) / 16;
// Assume heap size is 1 GB. We want to consume approximately
// 384MB of live data. Each InnerArray, including its referenced
// Integer objects, consumes approximately 512KB. 768 array
// elements * 512KB/array element = 384MB.
private static final int OUTER_ARRAY_SLOTS = 768;
private static final Random r = new Random(42);
private static int absolute(int arg) {
if (arg < 0) {
arg = -arg;
}
if (arg < 0) {
// negative of Integer.min_value EQUALS Integer.MIN_VALUE
arg = 0;
}
return arg;
}
private static int truncateAbsolute(int i) {
return absolute(i) % INNER_ARRAY_SLOTS;
}
private static long cpuIntensive(int n) {
long result = 1;
while (n >= 4) {
// arithmetic may overflow
result *= n;
n /= 4;
}
if (n > 0) {
result *= n;
}
return result;
}
private static Integer[] allocateEmptyInnerArray() {
Integer[] result = new Integer[INNER_ARRAY_SLOTS];
return result;
}
private static void fillArrayIntegersWithProbe(Integer[] array,
int spotCheckCount) {
for (int i = 0; i < INNER_INTEGERS; i++) {
int index = truncateAbsolute(r.nextInt());
int newValue = absolute(r.nextInt());
long newValueCPUIntensive = cpuIntensive(newValue);
boolean rejectThisValue = false;
// We just do a "spot check", because it consumes too much
// CPU time if we check all previous values.
for (int j = 0; j < spotCheckCount; j++) {
int spotIndex = truncateAbsolute(r.nextInt());
if ((array[spotIndex] != null) &&
(newValueCPUIntensive ==
cpuIntensive(array[spotIndex].intValue()))) {
rejectThisValue = true;
break;
}
}
if (rejectThisValue) {
i--;
} else {
// The same index value may be randomly generated
// multiple times, resulting in overwrite and garbage.
array[index] = Integer.valueOf(newValue);
}
}
}
// How much memory is represented by this array?
private static long doInventory(Integer[] array) {
int integerCount = 0;
if (array != null) {
for (int i = 0; i < INNER_ARRAY_SLOTS; i++) {
if (array[i] != null) {
integerCount++;
}
}
}
if (array == null) {
return 0;
} else {
return (INNER_ARRAY_SLOTS * 4L) + 16 + integerCount * 16L;
}
}
public static void main(String[] args) {
rootArray = new Integer[OUTER_ARRAY_SLOTS][];
long accumulator = 0;
// Fragment young, slowly so we don't do GC cycles here. We
// want the fragmented memory to accumulate in young.
// We don't want this memory to get promoted until last
// possible moment.
for (int index = 0; index < OUTER_ARRAY_SLOTS; index++) {
rootArray[index] = allocateEmptyInnerArray();
// Accumulate results to slow the allocation, so we have
// rare GC, long allocation runway.
accumulator += doInventory(rootArray[index]);
int inventoryIndex =
(index + OUTER_ARRAY_SLOTS - 16) % OUTER_ARRAY_SLOTS;
accumulator += doInventory(rootArray[inventoryIndex]);
inventoryIndex =
(index + OUTER_ARRAY_SLOTS - 32) % OUTER_ARRAY_SLOTS;
accumulator += doInventory(rootArray[inventoryIndex]);
inventoryIndex =
(index + OUTER_ARRAY_SLOTS - 64) % OUTER_ARRAY_SLOTS;
accumulator += doInventory(rootArray[inventoryIndex]);
}
// Fill the arrays slowly. We do this as slowly as possible to
// maximize allocation runway, separate GC cycles, accumulate
// promo potential. We want a big promo potential when we have
// highly fragmented young memory. This big promo potential
// must be paired with a large runway.
for (int j = 0; j < OUTER_ARRAY_SLOTS; j++) {
if (rootArray[j] != null) {
fillArrayIntegersWithProbe(rootArray[j], 2048);
accumulator += doInventory(rootArray[j]);
}
}
// The following assert simply confirms that the program ran
// correctly and prevents optimizers from removing what might
// appear to be dead code in the loops above. The
// expected regression failure consists of an assert failure
// observed with fast-debug builds of the JVM before resolution
// of JDK-8382085. The expected value of accumulator is
// determined empirically. The value may depend on the initial
// seed for random number generator and on various constants
// defined above which determine loop iterations.
Asserts.assertNotEquals(0L, accumulator,
"Proper execution is demonstrated by matching " +
"expected accumulator value with no JVM " +
"assert failures");
}
}