From 1536c8233fd7110b45a9c3ce33abf8a44733e2ed Mon Sep 17 00:00:00 2001 From: Xiaolong Peng Date: Mon, 20 Apr 2026 18:28:25 +0000 Subject: [PATCH] 8379531: Shenandoah: Allow safepoint preemption during allocation of very large arrays Reviewed-by: wkemper, kdnilsen --- .../shenandoah/shenandoahConcurrentMark.cpp | 6 +- .../gc/shenandoah/shenandoahGeneration.cpp | 9 - .../share/gc/shenandoah/shenandoahHeap.cpp | 6 + .../share/gc/shenandoah/shenandoahHeap.hpp | 1 + .../shenandoahObjArrayAllocator.cpp | 127 ++++++++++++ .../shenandoahObjArrayAllocator.hpp | 40 ++++ .../shenandoahRootProcessor.inline.hpp | 43 ++++ .../share/gc/shenandoah/shenandoahSTWMark.cpp | 6 + .../shenandoah/shenandoahThreadLocalData.cpp | 4 +- .../shenandoah/shenandoahThreadLocalData.hpp | 22 ++ .../gc/shenandoah/TestLargeArrayInit.java | 128 ++++++++++++ .../TestLargeArrayInitGCStress.java | 190 ++++++++++++++++++ .../gc/shenandoah/TestSmallArrayInit.java | 141 +++++++++++++ 13 files changed, 712 insertions(+), 11 deletions(-) create mode 100644 src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp create mode 100644 src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.hpp create mode 100644 test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java create mode 100644 test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java create mode 100644 test/hotspot/jtreg/gc/shenandoah/TestSmallArrayInit.java diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp index 367a15abfa4..fb716aef21e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp @@ -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"); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 5b26ee67653..9e9ad024511 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -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 } { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 4b01ea1cd52..5b2e050aa0a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -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) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index bed26a093d0..be40220e40f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -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; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp b/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp new file mode 100644 index 00000000000..e2215ea58ef --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp @@ -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(reinterpret_cast(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; +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.hpp b/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.hpp new file mode 100644 index 00000000000..7605b69eb7e --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.hpp @@ -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 diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp index 4504ac96819..fa24b2bab3f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp @@ -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* diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp index 5b4ce6d0bc9..73935ed4e91 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp @@ -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(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp index 1f3ce76cc1c..5295af51eff 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp @@ -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() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index b1b923bbfce..e9a5cf99fdd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -67,6 +67,9 @@ private: ShenandoahEvacuationStats* _evacuation_stats; + Atomic _invisible_root; + Atomic _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)); diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java new file mode 100644 index 00000000000..9ce254ff845 --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java @@ -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]); + } + } + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java new file mode 100644 index 00000000000..34f3b70197a --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java @@ -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]); + } + } + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSmallArrayInit.java b/test/hotspot/jtreg/gc/shenandoah/TestSmallArrayInit.java new file mode 100644 index 00000000000..3c15a140d18 --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/TestSmallArrayInit.java @@ -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 <= 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]); + } + } + } +}