8379531: Shenandoah: Allow safepoint preemption during allocation of very large arrays

Reviewed-by: wkemper, kdnilsen
This commit is contained in:
Xiaolong Peng 2026-04-20 18:28:25 +00:00
parent 7c4c04486f
commit 1536c8233f
13 changed files with 712 additions and 11 deletions

View File

@ -300,7 +300,11 @@ void ShenandoahConcurrentMark::finish_mark_work() {
default:
ShouldNotReachHere();
}
if (!generation()->is_old() && heap->is_concurrent_young_mark_in_progress()) {
// Lastly, ensure all the invisible roots are marked.
ShenandoahInvisibleRootsMarkClosure cl;
Threads::java_threads_do(&cl);
}
assert(task_queues()->is_empty(), "Should be empty");
}

View File

@ -284,15 +284,6 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) {
// along with the census done during marking, and compute the tenuring threshold.
ShenandoahAgeCensus* census = ShenandoahGenerationalHeap::heap()->age_census();
census->update_census(age0_pop);
#ifndef PRODUCT
size_t total_pop = age0_cl.get_total_population();
size_t total_census = census->get_total();
// Usually total_pop > total_census, but not by too much.
// We use integer division so anything up to just less than 2 is considered
// reasonable, and the "+1" is to avoid divide-by-zero.
assert((total_pop+1)/(total_census+1) == 1, "Extreme divergence: "
"%zu/%zu", total_pop, total_census);
#endif
}
{

View File

@ -62,6 +62,7 @@
#include "gc/shenandoah/shenandoahMarkingContext.inline.hpp"
#include "gc/shenandoah/shenandoahMemoryPool.hpp"
#include "gc/shenandoah/shenandoahMonitoringSupport.hpp"
#include "gc/shenandoah/shenandoahObjArrayAllocator.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
#include "gc/shenandoah/shenandoahPadding.hpp"
#include "gc/shenandoah/shenandoahParallelCleaning.inline.hpp"
@ -1073,6 +1074,11 @@ HeapWord* ShenandoahHeap::mem_allocate(size_t size) {
return allocate_memory(req);
}
oop ShenandoahHeap::array_allocate(Klass* klass, size_t size, int length, bool do_zero, TRAPS) {
ShenandoahObjArrayAllocator allocator(klass, size, length, do_zero, THREAD);
return allocator.allocate();
}
MetaWord* ShenandoahHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loader_data,
size_t size,
Metaspace::MetadataType mdtype) {

View File

@ -709,6 +709,7 @@ private:
public:
HeapWord* allocate_memory(ShenandoahAllocRequest& request);
HeapWord* mem_allocate(size_t size) override;
oop array_allocate(Klass* klass, size_t size, int length, bool do_zero, TRAPS) override;
MetaWord* satisfy_failed_metadata_allocation(ClassLoaderData* loader_data,
size_t size,
Metaspace::MetadataType mdtype) override;

View File

@ -0,0 +1,127 @@
/*
* 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.
*
*/
#include "gc/shared/memAllocator.hpp"
#include "gc/shenandoah/shenandoahBarrierSet.inline.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
#include "gc/shenandoah/shenandoahObjArrayAllocator.hpp"
#include "gc/shenandoah/shenandoahThreadLocalData.hpp"
#include "memory/universe.hpp"
#include "oops/arrayKlass.hpp"
#include "oops/arrayOop.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "utilities/copy.hpp"
#include "utilities/globalDefinitions.hpp"
ShenandoahObjArrayAllocator::ShenandoahObjArrayAllocator(
Klass* klass, size_t word_size, int length, bool do_zero, Thread* thread)
: ObjArrayAllocator(klass, word_size, length, do_zero, thread) {
assert(_length >= 0, "length should be non-negative");
}
oop ShenandoahObjArrayAllocator::initialize(HeapWord* mem) const {
// threshold of object size, while above this size current mutator will yield to safepoint
// when it clears the array content.
constexpr size_t THRESHOLD = 64 * K / BytesPerWord;
ShenandoahHeap* const heap = ShenandoahHeap::heap();
// Fast path: delegate to base class for small arrays or no-zero case.
// In no-zero case(_do_zero is false), the content of the array won't be zeroed, therefore no need to fall into slow-path.
if (!_do_zero || _word_size <= THRESHOLD) {
return ObjArrayAllocator::initialize(mem);
}
// Slow path: yield to safepoint when clearing for large arrays
// Compute clearing bounds
const BasicType element_type = ArrayKlass::cast(_klass)->element_type();
const size_t base_offset_in_bytes = (size_t)arrayOopDesc::base_offset_in_bytes(element_type);
const size_t process_start_offset_in_bytes = align_up(base_offset_in_bytes, (size_t)BytesPerWord);
const size_t process_start = process_start_offset_in_bytes / BytesPerWord;
const size_t process_size = _word_size - process_start;
// Pin the region before clearing to avoid moving the object until it is done
ShenandoahHeapRegion* region = heap->heap_region_containing(mem);
region->record_pin();
// Always initialize the mem with primitive array first so GC won't look into the elements in the array.
// For obj array, the header will be corrected to object array after clearing the memory.
Klass* filling_klass = _klass;
int filling_array_length = _length;
const bool is_ref_type = is_reference_type(element_type, true);
if (is_ref_type) {
const bool is_narrow_oop = element_type == T_NARROWOOP;
size_t filling_element_byte_size = is_narrow_oop ? T_INT_aelem_bytes : T_LONG_aelem_bytes;
filling_klass = is_narrow_oop ? Universe::intArrayKlass() : Universe::longArrayKlass();
filling_array_length = (int) ((process_size << LogBytesPerWord) / filling_element_byte_size);
}
ObjArrayAllocator filling_array_allocator(filling_klass, _word_size, filling_array_length , /* do_zero */ false);
filling_array_allocator.initialize(mem);
// Invisible roots will be scanned and marked at the end of marking.
ShenandoahThreadLocalData::set_invisible_root(_thread, mem, _word_size);
{
// The mem has been initialized as primitive array, the entire clearing work is safe for safepoint
ThreadBlockInVM tbivm(JavaThread::cast(_thread)); // Allow safepoint to proceed.
// Handle potential 4-byte alignment gap before array data
if (process_start_offset_in_bytes != base_offset_in_bytes) {
assert(process_start_offset_in_bytes - base_offset_in_bytes == 4, "Must be 4-byte aligned");
*reinterpret_cast<int*>(reinterpret_cast<char*>(mem) + base_offset_in_bytes) = 0;
}
Copy::zero_to_words(mem + process_start, process_size);
if (!is_ref_type) {
// zap paddings
mem_zap_start_padding(mem);
mem_zap_end_padding(mem);
}
}
// reference array, header need to be overridden to its own.
if (is_ref_type) {
arrayOopDesc::set_length(mem, _length);
finish(mem);
// zap paddings after setting correct klass
mem_zap_start_padding(mem);
mem_zap_end_padding(mem);
}
oop arrayObj = cast_to_oop(mem);
if (heap->is_concurrent_young_mark_in_progress() && !heap->marking_context()->allocated_after_mark_start(arrayObj)) {
// Keep the obj alive because we don't know the progress of marking,
// current concurrent marking could have done and VM is calling safepoint for final mark.
heap->keep_alive(arrayObj);
}
ShenandoahThreadLocalData::clear_invisible_root(_thread);
region->record_unpin();
return arrayObj;
}

View File

@ -0,0 +1,40 @@
/*
* 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.
*
*/
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHOBJARRAYALLOCATOR_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHOBJARRAYALLOCATOR_HPP
#include "gc/shared/memAllocator.hpp"
class ShenandoahObjArrayAllocator : public ObjArrayAllocator {
private:
// Override: clearing with safepoint yields for large arrays
oop initialize(HeapWord* mem) const override;
public:
ShenandoahObjArrayAllocator(Klass* klass, size_t word_size, int length,
bool do_zero, Thread* thread);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHOBJARRAYALLOCATOR_HPP

View File

@ -141,6 +141,49 @@ public:
}
};
class ShenandoahInvisibleRootsMarkClosure : public ThreadClosure {
public:
void do_thread(Thread* t) {
assert_at_safepoint();
HeapWord* invisible_root = ShenandoahThreadLocalData::get_invisible_root(t);
if (invisible_root == nullptr) {
return;
}
size_t invisible_root_word_size = ShenandoahThreadLocalData::get_invisible_root_word_size(t);
ShenandoahHeap* const heap = ShenandoahHeap::heap();
ShenandoahMarkingContext* const marking_context = heap->marking_context();
// Mark the invisible root if it is not marked.
if (!marking_context->is_marked(invisible_root)) {
bool was_upgraded = false;
if (!marking_context->mark_strong(cast_to_oop(invisible_root), was_upgraded)) {
return;
}
// Update region liveness data
ShenandoahHeapRegion* region = heap->heap_region_containing(invisible_root);
if (region->is_regular() || region->is_regular_pinned()) {
assert(!ShenandoahHeapRegion::requires_humongous(invisible_root_word_size), "Must not be humongous.");
region->increase_live_data_alloc_words(invisible_root_word_size);
} else if (region->is_humongous_start()) {
DEBUG_ONLY(size_t total_live_words = 0;)
do {
size_t current = region->get_live_data_words();
size_t region_used_words = region->used() >> LogHeapWordSize;
DEBUG_ONLY(total_live_words += region_used_words;)
assert(current == 0 || current == region_used_words, "Must be");
if (current == 0) {
region->increase_live_data_alloc_words(region_used_words);
}
region = heap->get_region(region->index() + 1);
} while (region != nullptr && region->is_humongous_continuation());
assert(total_live_words == invisible_root_word_size, "Must be");
}
}
}
};
// The rationale for selecting the roots to scan is as follows:
// a. With unload_classes = true, we only want to scan the actual strong roots from the
// code cache. This will allow us to identify the dead classes, unload them, *and*

View File

@ -104,6 +104,12 @@ void ShenandoahSTWMark::mark() {
heap->workers()->run_task(&task);
assert(task_queues()->is_empty(), "Should be empty");
if (!generation()->is_old()) {
// Lastly, ensure all the invisible roots are marked.
ShenandoahInvisibleRootsMarkClosure cl;
Threads::java_threads_do(&cl);
}
}
_generation->set_mark_complete();

View File

@ -38,7 +38,9 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() :
_gclab(nullptr),
_gclab_size(0),
_shenandoah_plab(nullptr),
_evacuation_stats(new ShenandoahEvacuationStats()) {
_evacuation_stats(new ShenandoahEvacuationStats()),
_invisible_root(nullptr),
_invisible_root_word_size(0) {
}
ShenandoahThreadLocalData::~ShenandoahThreadLocalData() {

View File

@ -67,6 +67,9 @@ private:
ShenandoahEvacuationStats* _evacuation_stats;
Atomic<HeapWord*> _invisible_root;
Atomic<size_t> _invisible_root_word_size;
ShenandoahThreadLocalData();
~ShenandoahThreadLocalData();
@ -206,6 +209,25 @@ public:
static ByteSize card_table_offset() {
return Thread::gc_data_offset() + byte_offset_of(ShenandoahThreadLocalData, _card_table);
}
// invisible root are the partially initialized obj array set by ShenandoahObjArrayAllocator
static void set_invisible_root(Thread* thread, HeapWord* invisible_root, size_t word_size) {
data(thread)->_invisible_root.store_relaxed(invisible_root);
data(thread)->_invisible_root_word_size.store_relaxed(word_size);
}
static void clear_invisible_root(Thread* thread) {
data(thread)->_invisible_root.store_relaxed(nullptr);
data(thread)->_invisible_root_word_size.store_relaxed(0);
}
static HeapWord* get_invisible_root(Thread* thread) {
return data(thread)->_invisible_root.load_relaxed();
}
static size_t get_invisible_root_word_size(Thread* thread) {
return data(thread)->_invisible_root_word_size.load_relaxed();
}
};
STATIC_ASSERT(sizeof(ShenandoahThreadLocalData) <= sizeof(GCThreadLocalData));

View File

@ -0,0 +1,128 @@
/*
* 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.
*
*/
/*
* @test id=adaptive
* @summary Verify zero-initialization completeness for large arrays under Shenandoah adaptive mode
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx512m -Xms512m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive
* TestLargeArrayInit
*/
/*
* @test id=generational
* @summary Verify zero-initialization completeness for large arrays under Shenandoah generational mode
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx512m -Xms512m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational
* TestLargeArrayInit
*/
/*
* @test id=compact-on
* @summary Verify zero-initialization completeness for large arrays with compact object headers enabled
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx512m -Xms512m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive
* -XX:+UseCompactObjectHeaders
* TestLargeArrayInit
*/
/*
* @test id=compact-off
* @summary Verify zero-initialization completeness for large arrays with compact object headers disabled
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx512m -Xms512m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive
* -XX:-UseCompactObjectHeaders
* TestLargeArrayInit
*/
/**
*
* Allocates large byte[], int[], long[], and Object[] arrays whose sizes span
* multiple 64K-word segments (~100MB each), then verifies every element is
* zero (or null for reference arrays).
*/
public class TestLargeArrayInit {
// ~100MB for each array type
static final int BYTE_LEN = 100 * 1024 * 1024; // 100M elements
static final int INT_LEN = 25 * 1024 * 1024; // 25M elements * 4 bytes = 100MB
static final int LONG_LEN = 12 * 1024 * 1024 + 512*1024; // ~100MB in longs
static final int OBJ_LEN = 12 * 1024 * 1024 + 512*1024; // ~100MB in refs (8 bytes each on 64-bit)
public static void main(String[] args) {
testByteArray();
testIntArray();
testLongArray();
testObjectArray();
System.out.println("TestLargeArrayInit PASSED");
}
static void testByteArray() {
byte[] arr = new byte[BYTE_LEN];
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
throw new RuntimeException("byte[] not zero at index " + i + ": " + arr[i]);
}
}
}
static void testIntArray() {
int[] arr = new int[INT_LEN];
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
throw new RuntimeException("int[] not zero at index " + i + ": " + arr[i]);
}
}
}
static void testLongArray() {
long[] arr = new long[LONG_LEN];
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0L) {
throw new RuntimeException("long[] not zero at index " + i + ": " + arr[i]);
}
}
}
static void testObjectArray() {
Object[] arr = new Object[OBJ_LEN];
for (int i = 0; i < arr.length; i++) {
if (arr[i] != null) {
throw new RuntimeException("Object[] not null at index " + i + ": " + arr[i]);
}
}
}
}

View File

@ -0,0 +1,190 @@
/*
* 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.
*
*/
/*
* @test id=aggressive
* @summary Verify correct object metadata for large arrays under Shenandoah GC stress (aggressive heuristics)
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive
* TestLargeArrayInitGCStress
*/
/*
* @test id=generational-aggressive
* @summary Verify correct object metadata for large arrays under Shenandoah generational mode with aggressive heuristics
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive -XX:ShenandoahGCMode=generational
* TestLargeArrayInitGCStress
*/
/*
* @test id=compact-on
* @summary Verify correct object metadata for large arrays with compact object headers enabled
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive
* -XX:+UseCompactObjectHeaders
* TestLargeArrayInitGCStress
*/
/*
* @test id=compact-off
* @summary Verify correct object metadata for large arrays with compact object headers disabled
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive
* -XX:-UseCompactObjectHeaders
* TestLargeArrayInitGCStress
*/
/**
*
* Allocates large arrays of various types under GC stress (aggressive heuristics,
* tight 256MB heap) and verifies that after each allocation:
* - array.length matches the requested length
* - array.getClass() matches the expected array type
* - All elements are zero/null
*
* Arrays are sized to span multiple 64K-word segments to exercise the segmented
* clearing path in ShenandoahObjArrayAllocator. The loop creates sustained GC
* pressure so that safepoints are likely to occur during array initialization.
*/
public class TestLargeArrayInitGCStress {
static final int ITERATIONS = 50;
// Array sizes: small (within one segment), medium (a few segments), large (many segments)
static final int[] BYTE_SIZES = {1024, 256 * 1024, 2 * 1024 * 1024, 16 * 1024 * 1024};
static final int[] INT_SIZES = {1024, 64 * 1024, 512 * 1024, 4 * 1024 * 1024};
static final int[] LONG_SIZES = {1024, 32 * 1024, 256 * 1024, 2 * 1024 * 1024};
static final int[] OBJ_SIZES = {1024, 32 * 1024, 256 * 1024, 2 * 1024 * 1024};
// Volatile sink to prevent dead code elimination
static volatile Object sink;
public static void main(String[] args) {
for (int iter = 0; iter < ITERATIONS; iter++) {
testByteArrays();
testIntArrays();
testLongArrays();
testObjectArrays();
}
System.out.println("TestLargeArrayInitGCStress PASSED");
}
static void testByteArrays() {
for (int len : BYTE_SIZES) {
byte[] arr = new byte[len];
sink = arr;
verifyLength(arr.length, len, "byte[]");
verifyClass(arr.getClass(), byte[].class, "byte[]");
verifyByteZeros(arr, len);
}
}
static void testIntArrays() {
for (int len : INT_SIZES) {
int[] arr = new int[len];
sink = arr;
verifyLength(arr.length, len, "int[]");
verifyClass(arr.getClass(), int[].class, "int[]");
verifyIntZeros(arr, len);
}
}
static void testLongArrays() {
for (int len : LONG_SIZES) {
long[] arr = new long[len];
sink = arr;
verifyLength(arr.length, len, "long[]");
verifyClass(arr.getClass(), long[].class, "long[]");
verifyLongZeros(arr, len);
}
}
static void testObjectArrays() {
for (int len : OBJ_SIZES) {
Object[] arr = new Object[len];
sink = arr;
verifyLength(arr.length, len, "Object[]");
verifyClass(arr.getClass(), Object[].class, "Object[]");
verifyObjectNulls(arr, len);
}
}
static void verifyLength(int actual, int expected, String type) {
if (actual != expected) {
throw new RuntimeException(type + " length mismatch: expected " + expected + ", got " + actual);
}
}
static void verifyClass(Class<?> actual, Class<?> expected, String type) {
if (actual != expected) {
throw new RuntimeException(type + " class mismatch: expected " + expected.getName() + ", got " + actual.getName());
}
}
static void verifyByteZeros(byte[] arr, int len) {
for (int i = 0; i < len; i++) {
if (arr[i] != 0) {
throw new RuntimeException("byte[] not zero at index " + i + ": " + arr[i]);
}
}
}
static void verifyIntZeros(int[] arr, int len) {
for (int i = 0; i < len; i++) {
if (arr[i] != 0) {
throw new RuntimeException("int[] not zero at index " + i + ": " + arr[i]);
}
}
}
static void verifyLongZeros(long[] arr, int len) {
for (int i = 0; i < len; i++) {
if (arr[i] != 0L) {
throw new RuntimeException("long[] not zero at index " + i + ": " + arr[i]);
}
}
}
static void verifyObjectNulls(Object[] arr, int len) {
for (int i = 0; i < len; i++) {
if (arr[i] != null) {
throw new RuntimeException("Object[] not null at index " + i + ": " + arr[i]);
}
}
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.
*
*/
/*
* @test id=adaptive
* @summary Verify behavioral equivalence for small arrays under Shenandoah adaptive mode
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive
* TestSmallArrayInit
*/
/*
* @test id=generational
* @summary Verify behavioral equivalence for small arrays under Shenandoah generational mode
* @requires vm.gc.Shenandoah
* @library /test/lib
*
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m
* -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational
* TestSmallArrayInit
*/
/**
*
* For arrays with word_size &lt;= 64K words (the segment size threshold),
* ShenandoahObjArrayAllocator delegates to ObjArrayAllocator::initialize().
* This test verifies that small arrays of various types and sizes are correctly
* zero-initialized, confirming no regression vs. the default allocator behavior.
*
* Test sizes:
* - Tiny: 10 elements
* - Small: 1000 elements
* - Near boundary: just under 64K words (65536 words = 524288 bytes on 64-bit)
* byte[] -> 524288 elements (524288 bytes = 64K words)
* int[] -> 131072 elements (524288 bytes = 64K words)
* long[] -> 65536 elements (524288 bytes = 64K words)
* Object[] -> 65536 elements (524288 bytes = 64K words, 8 bytes/ref)
*/
public class TestSmallArrayInit {
// Tiny sizes
static final int TINY = 10;
// Small sizes
static final int SMALL = 1000;
// Near 64K-word boundary sizes (just under 524288 bytes of element data)
// 64K words = 65536 words = 524288 bytes on 64-bit
static final int NEAR_BOUNDARY_BYTE = 524288; // 524288 bytes
static final int NEAR_BOUNDARY_INT = 131072; // 131072 * 4 = 524288 bytes
static final int NEAR_BOUNDARY_LONG = 65536; // 65536 * 8 = 524288 bytes
static final int NEAR_BOUNDARY_OBJ = 65536; // 65536 * 8 = 524288 bytes (8 bytes/ref on 64-bit)
public static void main(String[] args) {
testByteArrays();
testIntArrays();
testLongArrays();
testObjectArrays();
System.out.println("TestSmallArrayInit PASSED");
}
static void testByteArrays() {
verifyByteArray(new byte[TINY], "tiny");
verifyByteArray(new byte[SMALL], "small");
verifyByteArray(new byte[NEAR_BOUNDARY_BYTE], "near-boundary");
}
static void testIntArrays() {
verifyIntArray(new int[TINY], "tiny");
verifyIntArray(new int[SMALL], "small");
verifyIntArray(new int[NEAR_BOUNDARY_INT], "near-boundary");
}
static void testLongArrays() {
verifyLongArray(new long[TINY], "tiny");
verifyLongArray(new long[SMALL], "small");
verifyLongArray(new long[NEAR_BOUNDARY_LONG], "near-boundary");
}
static void testObjectArrays() {
verifyObjectArray(new Object[TINY], "tiny");
verifyObjectArray(new Object[SMALL], "small");
verifyObjectArray(new Object[NEAR_BOUNDARY_OBJ], "near-boundary");
}
static void verifyByteArray(byte[] arr, String label) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
throw new RuntimeException("byte[" + label + "] not zero at index " + i + ": " + arr[i]);
}
}
}
static void verifyIntArray(int[] arr, String label) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
throw new RuntimeException("int[" + label + "] not zero at index " + i + ": " + arr[i]);
}
}
}
static void verifyLongArray(long[] arr, String label) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0L) {
throw new RuntimeException("long[" + label + "] not zero at index " + i + ": " + arr[i]);
}
}
}
static void verifyObjectArray(Object[] arr, String label) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] != null) {
throw new RuntimeException("Object[" + label + "] not null at index " + i + ": " + arr[i]);
}
}
}
}