Shenandoah: Introduce per-partition allocator instances with retained region optimization

This commit is contained in:
Xiaolong Peng 2026-05-28 14:52:35 -07:00
parent 584e424a5e
commit 3e4ca84d66
7 changed files with 497 additions and 390 deletions

View File

@ -44,6 +44,9 @@ public:
// Allocate memory for the given request. Returns nullptr on failure.
// Sets in_new_region to true if allocation consumes a previously empty region.
virtual HeapWord* allocate(ShenandoahAllocRequest& req, bool& in_new_region) = 0;
// Called during free-set rebuild to invalidate any cached allocation state.
virtual void clear_retained_regions() = 0;
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHALLOCATOR_HPP

View File

@ -26,6 +26,7 @@
#include "gc/shared/tlab_globals.hpp"
#include "gc/shenandoah/shenandoahAffiliation.hpp"
#include "gc/shenandoah/shenandoahAllocator.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegionSet.hpp"
@ -2604,6 +2605,8 @@ void ShenandoahFreeSet::prepare_to_rebuild(size_t &young_trashed_regions, size_t
shenandoah_assert_heaplocked();
assert(rebuild_lock() != nullptr, "sanity");
rebuild_lock()->lock(false);
// Invalidate any retained regions in the allocator before clearing partition state.
_heap->allocator()->clear_retained_regions();
// This resets all state information, removing all regions from all sets.
clear();
log_debug(gc, free)("Rebuilding FreeSet");

View File

@ -441,6 +441,7 @@ public:
class ShenandoahFreeSet : public CHeapObj<mtGC> {
friend class ShenandoahAllocator;
friend class ShenandoahSerialAllocator;
template<ShenandoahFreeSetPartitionId> friend class ShenandoahPartitionAllocator;
using idx_t = ShenandoahSimpleBitMap::idx_t;
private:

View File

@ -0,0 +1,378 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* DO NOT ALTER OR REMOVE THIS COPYRIGHT NOTICE 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/cardTable.hpp"
#include "gc/shared/plab.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
#include "gc/shenandoah/shenandoahMarkingContext.inline.hpp"
#include "gc/shenandoah/shenandoahPartitionAllocator.hpp"
#include "logging/log.hpp"
using idx_t = ShenandoahSimpleBitMap::idx_t;
class ShenandoahLeftRightIterator {
private:
idx_t _idx;
idx_t _end;
ShenandoahRegionPartitions* _partitions;
ShenandoahFreeSetPartitionId _partition;
public:
explicit ShenandoahLeftRightIterator(ShenandoahRegionPartitions* partitions,
ShenandoahFreeSetPartitionId partition, bool use_empty = false)
: _idx(0), _end(0), _partitions(partitions), _partition(partition) {
_idx = use_empty ? _partitions->leftmost_empty(_partition) : _partitions->leftmost(_partition);
_end = use_empty ? _partitions->rightmost_empty(_partition) : _partitions->rightmost(_partition);
}
bool has_next() const {
if (_idx <= _end) {
assert(_partitions->in_free_set(_partition, _idx), "Boundaries or find_last_set_bit failed: %zd", _idx);
return true;
}
return false;
}
idx_t current() const { return _idx; }
idx_t next() {
_idx = _partitions->find_index_of_next_available_region(_partition, _idx + 1);
return current();
}
};
class ShenandoahRightLeftIterator {
private:
idx_t _idx;
idx_t _end;
ShenandoahRegionPartitions* _partitions;
ShenandoahFreeSetPartitionId _partition;
public:
explicit ShenandoahRightLeftIterator(ShenandoahRegionPartitions* partitions,
ShenandoahFreeSetPartitionId partition, bool use_empty = false)
: _idx(0), _end(0), _partitions(partitions), _partition(partition) {
_idx = use_empty ? _partitions->rightmost_empty(_partition) : _partitions->rightmost(_partition);
_end = use_empty ? _partitions->leftmost_empty(_partition) : _partitions->leftmost(_partition);
}
bool has_next() const {
if (_idx >= _end) {
assert(_partitions->in_free_set(_partition, _idx), "Boundaries or find_last_set_bit failed: %zd", _idx);
return true;
}
return false;
}
idx_t current() const { return _idx; }
idx_t next() {
_idx = _partitions->find_index_of_previous_available_region(_partition, _idx - 1);
return current();
}
};
template<ShenandoahFreeSetPartitionId PARTITION>
ShenandoahPartitionAllocator<PARTITION>::ShenandoahPartitionAllocator(ShenandoahFreeSet* free_set)
: _free_set(free_set),
_heap(ShenandoahHeap::heap()),
_retained_region(nullptr),
_alloc_bias_weight(INITIAL_ALLOC_BIAS_WEIGHT) {}
template<ShenandoahFreeSetPartitionId PARTITION>
HeapWord* ShenandoahPartitionAllocator<PARTITION>::allocate(ShenandoahAllocRequest& req, bool& in_new_region) {
// Fast path: try the retained region from the previous allocation.
if (_retained_region != nullptr) {
size_t min_size = req.is_lab_alloc() ? req.min_size() : req.size();
if (_free_set->alloc_capacity(_retained_region) >= min_size * HeapWordSize) {
HeapWord* result = try_allocate_in(_retained_region, req, in_new_region);
if (result != nullptr) {
return result;
}
}
_retained_region = nullptr;
}
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
if constexpr (PARTITION == ShenandoahFreeSetPartitionId::Mutator) {
// Mutator allocation: use left/right bias alternation.
update_allocation_bias();
if (partitions.is_empty(PARTITION)) {
return nullptr;
}
if (partitions.alloc_from_left_bias(PARTITION)) {
ShenandoahLeftRightIterator iterator(&partitions, PARTITION);
return allocate_from_regions(iterator, req, in_new_region);
}
ShenandoahRightLeftIterator iterator(&partitions, PARTITION);
return allocate_from_regions(iterator, req, in_new_region);
} else {
// Collector/OldCollector: prefer regions matching affiliation, then overflow from Mutator.
HeapWord* result = nullptr;
if (partitions.alloc_from_left_bias(PARTITION)) {
ShenandoahLeftRightIterator iterator(&partitions, PARTITION);
result = allocate_with_affiliation(iterator, req.affiliation(), req, in_new_region);
} else {
ShenandoahRightLeftIterator iterator(&partitions, PARTITION);
result = allocate_with_affiliation(iterator, req.affiliation(), req, in_new_region);
}
if (result != nullptr) {
return result;
}
if (!ShenandoahEvacReserveOverflow) {
return nullptr;
}
// Overflow: flip an empty region from Mutator partition, then allocate in it.
if (partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::Mutator) > 0) {
result = try_allocate_from_mutator(req, in_new_region);
}
return result;
}
}
// Alternating allocation direction between GC passes improves evacuation performance by
// consuming partially-used regions before they become uncollectable floating garbage.
template<ShenandoahFreeSetPartitionId PARTITION>
void ShenandoahPartitionAllocator<PARTITION>::update_allocation_bias() {
if (_alloc_bias_weight-- <= 0) {
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
idx_t non_empty_on_left = (partitions.leftmost_empty(PARTITION) - partitions.leftmost(PARTITION));
idx_t non_empty_on_right = (partitions.rightmost(PARTITION) - partitions.rightmost_empty(PARTITION));
partitions.set_bias_from_left_to_right(PARTITION, (non_empty_on_right < non_empty_on_left));
_alloc_bias_weight = INITIAL_ALLOC_BIAS_WEIGHT;
}
}
template<ShenandoahFreeSetPartitionId PARTITION>
template<typename Iter>
HeapWord* ShenandoahPartitionAllocator<PARTITION>::allocate_from_regions(Iter& iterator, ShenandoahAllocRequest& req, bool& in_new_region) {
for (idx_t idx = iterator.current(); iterator.has_next(); idx = iterator.next()) {
ShenandoahHeapRegion* r = _heap->get_region(idx);
size_t min_size = req.is_lab_alloc() ? req.min_size() : req.size();
if (_free_set->alloc_capacity(r) >= min_size * HeapWordSize) {
HeapWord* result = try_allocate_in(r, req, in_new_region);
if (result != nullptr) {
return result;
}
}
}
return nullptr;
}
template<ShenandoahFreeSetPartitionId PARTITION>
template<typename Iter>
HeapWord* ShenandoahPartitionAllocator<PARTITION>::allocate_with_affiliation(Iter& iterator,
ShenandoahAffiliation affiliation,
ShenandoahAllocRequest& req,
bool& in_new_region) {
assert(affiliation != ShenandoahAffiliation::FREE, "Must not");
ShenandoahHeapRegion* free_region = nullptr;
for (idx_t idx = iterator.current(); iterator.has_next(); idx = iterator.next()) {
ShenandoahHeapRegion* r = _heap->get_region(idx);
if (r->affiliation() == affiliation) {
HeapWord* result = try_allocate_in(r, req, in_new_region);
if (result != nullptr) {
return result;
}
} else if (free_region == nullptr && r->affiliation() == FREE) {
free_region = r;
}
}
if (free_region != nullptr) {
HeapWord* result = try_allocate_in(free_region, req, in_new_region);
assert(result != nullptr, "Allocate in free region in the partition always succeed.");
return result;
}
log_debug(gc, free)("Could not allocate collector region with affiliation: %s for request " PTR_FORMAT,
shenandoah_affiliation_name(affiliation), p2i(&req));
return nullptr;
}
// Flip an empty region from Mutator to this collector partition, then allocate in it.
// Searches from right to left to keep longer-lived collector regions at high addresses.
template<ShenandoahFreeSetPartitionId PARTITION>
HeapWord* ShenandoahPartitionAllocator<PARTITION>::try_allocate_from_mutator(ShenandoahAllocRequest& req, bool& in_new_region) {
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
ShenandoahRightLeftIterator iterator(&partitions, ShenandoahFreeSetPartitionId::Mutator, true);
for (idx_t idx = iterator.current(); iterator.has_next(); idx = iterator.next()) {
ShenandoahHeapRegion* r = _heap->get_region(idx);
if (_free_set->can_allocate_from(r)) {
if (req.is_old()) {
if (!_free_set->flip_to_old_gc(r)) {
continue;
}
} else {
_free_set->flip_to_gc(r);
}
log_debug(gc, free)("Flipped region %zu to gc for request: " PTR_FORMAT, idx, p2i(&req));
return try_allocate_in(r, req, in_new_region);
}
}
return nullptr;
}
// Allocate within region r. Handles region recycling, LAB sizing, PLAB alignment,
// partition accounting, and region retirement.
template<ShenandoahFreeSetPartitionId PARTITION>
HeapWord* ShenandoahPartitionAllocator<PARTITION>::try_allocate_in(ShenandoahHeapRegion* r, ShenandoahAllocRequest& req, bool& in_new_region) {
assert(_free_set->has_alloc_capacity(r), "Performance: should avoid full regions on this path: %zu", r->index());
ShenandoahHeap* heap = _heap;
// Trash regions cannot be used while weak roots processing needs accurate marking info.
if (heap->is_concurrent_weak_root_in_progress() && r->is_trash()) {
return nullptr;
}
HeapWord* result = nullptr;
r->try_recycle_under_lock();
in_new_region = r->is_empty();
if (in_new_region) {
log_debug(gc, free)("Using new region (%zu) for %s (" PTR_FORMAT ").",
r->index(), req.type_string(), p2i(&req));
assert(!r->is_affiliated(), "New region %zu should be unaffiliated", r->index());
r->set_affiliation(req.affiliation());
if (r->is_old()) {
r->end_preemptible_coalesce_and_fill();
}
#ifdef ASSERT
ShenandoahMarkingContext* const ctx = heap->marking_context();
assert(ctx->top_at_mark_start(r) == r->bottom(), "Newly established allocation region starts with TAMS equal to bottom");
assert(ctx->is_bitmap_range_within_region_clear(ctx->top_bitmap(r), r->end()), "Bitmap above top_bitmap() must be clear");
#endif
} else {
assert(r->is_affiliated(), "Region %zu that is not new should be affiliated", r->index());
if (r->affiliation() != req.affiliation()) {
assert(heap->mode()->is_generational(), "Request for %s from %s region should only happen in generational mode.",
req.affiliation_name(), r->affiliation_name());
return nullptr;
}
}
// Perform the actual allocation: LABs may be shrunk to fit, PLABs must be card-aligned.
if (req.is_lab_alloc()) {
size_t adjusted_size = req.size();
size_t free = r->free();
if (req.is_old()) {
assert(heap->mode()->is_generational(), "PLABs are only for generational mode");
assert(_free_set->_partitions.in_free_set(ShenandoahFreeSetPartitionId::OldCollector, r->index()),
"PLABS must be allocated in old_collector_free regions");
size_t usable_free = _free_set->get_usable_free_words(free);
if (adjusted_size > usable_free) {
adjusted_size = usable_free;
}
adjusted_size = align_down(adjusted_size, CardTable::card_size_in_words());
if (adjusted_size >= req.min_size()) {
result = _free_set->allocate_aligned_plab(adjusted_size, req, r);
assert(result != nullptr, "allocate must succeed");
req.set_actual_size(adjusted_size);
} else {
log_trace(gc, free)("Failed to shrink PLAB request (%zu) in region %zu to %zu"
" because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
}
} else {
free = align_down(free >> LogHeapWordSize, MinObjAlignment);
if (adjusted_size > free) {
adjusted_size = free;
}
if (adjusted_size >= req.min_size()) {
result = r->allocate(adjusted_size, req);
assert(result != nullptr, "Allocation must succeed: free %zu, actual %zu", free, adjusted_size);
req.set_actual_size(adjusted_size);
} else {
log_trace(gc, free)("Failed to shrink TLAB or GCLAB request (%zu) in region %zu to %zu"
" because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
}
}
} else {
size_t size = req.size();
result = r->allocate(size, req);
if (result != nullptr) {
req.set_actual_size(size);
}
}
// Update partition used bytes on success.
if (result != nullptr) {
if constexpr (PARTITION == ShenandoahFreeSetPartitionId::Mutator) {
assert(req.is_young(), "Mutator allocations always come from young generation.");
_free_set->_partitions.increase_used(PARTITION, req.actual_size() * HeapWordSize);
} else {
assert(req.is_gc_alloc(), "Should be gc_alloc since req wasn't mutator alloc");
// GC allocations set update_watermark so relocated objects aren't re-updated during update-refs.
r->set_update_watermark(r->top());
_free_set->_partitions.increase_used(PARTITION, (req.actual_size() + req.waste()) * HeapWordSize);
}
}
DEBUG_ONLY(bool boundary_changed = false;)
if ((result != nullptr) && in_new_region) {
_free_set->_partitions.one_region_is_no_longer_empty(PARTITION);
DEBUG_ONLY(boundary_changed = true;)
}
// Retire the region if remaining capacity is too small for any future PLAB.
if (_free_set->alloc_capacity(r) < PLAB::min_size() * HeapWordSize) {
size_t idx = r->index();
size_t waste_bytes = _free_set->_partitions.retire_from_partition(PARTITION, idx, r->used());
DEBUG_ONLY(boundary_changed = true;)
if constexpr (PARTITION == ShenandoahFreeSetPartitionId::Mutator) {
if (waste_bytes > 0) {
req.set_waste(waste_bytes / HeapWordSize);
}
}
if (_retained_region == r) {
_retained_region = nullptr;
}
} else if (result != nullptr) {
// Region still has usable capacity — retain for next allocation.
_retained_region = r;
}
// Recompute generation used/affiliated totals.
_free_set->notify_allocation(PARTITION, in_new_region);
#ifdef ASSERT
if (boundary_changed) {
_free_set->_partitions.assert_bounds();
} else {
_free_set->_partitions.assert_bounds_sanity();
}
#endif
return result;
}
// Explicit template instantiations for all partitions.
template class ShenandoahPartitionAllocator<ShenandoahFreeSetPartitionId::Mutator>;
template class ShenandoahPartitionAllocator<ShenandoahFreeSetPartitionId::Collector>;
template class ShenandoahPartitionAllocator<ShenandoahFreeSetPartitionId::OldCollector>;

View File

@ -0,0 +1,88 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* DO NOT ALTER OR REMOVE THIS COPYRIGHT NOTICE 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_SHENANDOAHPARTITIONALLOCATOR_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHPARTITIONALLOCATOR_HPP
#include "gc/shenandoah/shenandoahAffiliation.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "memory/allocation.hpp"
class ShenandoahAllocRequest;
class ShenandoahHeap;
class ShenandoahHeapRegion;
class ShenandoahSerialAllocator;
// ShenandoahPartitionAllocator handles allocation within a single free-set partition.
// Templated on partition ID so that partition-specific behavior (bias for Mutator,
// affiliation preference for Collector/OldCollector) is resolved at compile time.
template<ShenandoahFreeSetPartitionId PARTITION>
class ShenandoahPartitionAllocator : public CHeapObj<mtGC> {
friend class ShenandoahSerialAllocator;
private:
ShenandoahFreeSet* const _free_set;
ShenandoahHeap* const _heap;
// Last region that had remaining capacity after allocation. Checked first on next request
// to avoid scanning the partition bitmap. Cleared on free-set rebuild.
ShenandoahHeapRegion* _retained_region;
// Allocation direction alternates to pack allocations tightly. Only used for Mutator.
ssize_t _alloc_bias_weight;
static const ssize_t INITIAL_ALLOC_BIAS_WEIGHT = 256;
// Attempt allocation within a single region. Handles LAB sizing, PLAB card-alignment,
// affiliation checks, and updates partition accounting via ShenandoahFreeSet.
// Retires the region if remaining capacity falls below PLAB::min_size().
HeapWord* try_allocate_in(ShenandoahHeapRegion* r, ShenandoahAllocRequest& req, bool& in_new_region);
// Re-evaluate left-to-right vs right-to-left bias. Only meaningful for Mutator partition.
void update_allocation_bias();
// Scan regions using the given iterator, attempt allocation in each.
template<typename Iter>
HeapWord* allocate_from_regions(Iter& iterator, ShenandoahAllocRequest& req, bool& in_new_region);
// For collector partitions: prefer regions matching the requested affiliation, fall back to FREE.
template<typename Iter>
HeapWord* allocate_with_affiliation(Iter& iterator,
ShenandoahAffiliation affiliation,
ShenandoahAllocRequest& req,
bool& in_new_region);
// Flip an empty region from Mutator partition to this collector partition, then allocate in it.
HeapWord* try_allocate_from_mutator(ShenandoahAllocRequest& req, bool& in_new_region);
public:
ShenandoahPartitionAllocator(ShenandoahFreeSet* free_set);
// Allocate from this partition. Returns nullptr if partition cannot satisfy the request.
HeapWord* allocate(ShenandoahAllocRequest& req, bool& in_new_region);
// Must be called when the free set is rebuilt to invalidate retained regions.
void clear_retained_regions() { _retained_region = nullptr; }
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHPARTITIONALLOCATOR_HPP

View File

@ -22,89 +22,20 @@
*
*/
#include "gc/shared/cardTable.hpp"
#include "gc/shared/plab.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
#include "gc/shenandoah/shenandoahMarkingContext.inline.hpp"
#include "gc/shenandoah/shenandoahSerialAllocator.hpp"
#include "logging/log.hpp"
using idx_t = ShenandoahSimpleBitMap::idx_t;
class ShenandoahLeftRightIterator {
private:
idx_t _idx;
idx_t _end;
ShenandoahRegionPartitions* _partitions;
ShenandoahFreeSetPartitionId _partition;
public:
explicit ShenandoahLeftRightIterator(ShenandoahRegionPartitions* partitions,
ShenandoahFreeSetPartitionId partition, bool use_empty = false)
: _idx(0), _end(0), _partitions(partitions), _partition(partition) {
_idx = use_empty ? _partitions->leftmost_empty(_partition) : _partitions->leftmost(_partition);
_end = use_empty ? _partitions->rightmost_empty(_partition) : _partitions->rightmost(_partition);
}
bool has_next() const {
if (_idx <= _end) {
assert(_partitions->in_free_set(_partition, _idx), "Boundaries or find_last_set_bit failed: %zd", _idx);
return true;
}
return false;
}
idx_t current() const {
return _idx;
}
idx_t next() {
_idx = _partitions->find_index_of_next_available_region(_partition, _idx + 1);
return current();
}
};
class ShenandoahRightLeftIterator {
private:
idx_t _idx;
idx_t _end;
ShenandoahRegionPartitions* _partitions;
ShenandoahFreeSetPartitionId _partition;
public:
explicit ShenandoahRightLeftIterator(ShenandoahRegionPartitions* partitions,
ShenandoahFreeSetPartitionId partition, bool use_empty = false)
: _idx(0), _end(0), _partitions(partitions), _partition(partition) {
_idx = use_empty ? _partitions->rightmost_empty(_partition) : _partitions->rightmost(_partition);
_end = use_empty ? _partitions->leftmost_empty(_partition) : _partitions->leftmost(_partition);
}
bool has_next() const {
if (_idx >= _end) {
assert(_partitions->in_free_set(_partition, _idx), "Boundaries or find_last_set_bit failed: %zd", _idx);
return true;
}
return false;
}
idx_t current() const {
return _idx;
}
idx_t next() {
_idx = _partitions->find_index_of_previous_available_region(_partition, _idx - 1);
return current();
}
};
ShenandoahSerialAllocator::ShenandoahSerialAllocator(ShenandoahFreeSet* free_set)
: ShenandoahAllocator(free_set),
_heap(ShenandoahHeap::heap()),
_alloc_bias_weight(INITIAL_ALLOC_BIAS_WEIGHT) {}
_mutator_alloc(free_set),
_collector_alloc(free_set),
_old_collector_alloc(free_set) {}
HeapWord* ShenandoahSerialAllocator::allocate(ShenandoahAllocRequest& req, bool& in_new_region) {
shenandoah_assert_heaplocked();
if (ShenandoahHeapRegion::requires_humongous(req.size())) {
switch (req.type()) {
case ShenandoahAllocRequest::_alloc_shared:
@ -124,286 +55,20 @@ HeapWord* ShenandoahSerialAllocator::allocate(ShenandoahAllocRequest& req, bool&
ShouldNotReachHere();
return nullptr;
}
} else {
return allocate_single(req, in_new_region);
}
}
HeapWord* ShenandoahSerialAllocator::allocate_single(ShenandoahAllocRequest& req, bool& in_new_region) {
shenandoah_assert_heaplocked();
// Dispatch to the appropriate per-partition allocator.
if (req.is_mutator_alloc()) {
return allocate_for_mutator(req, in_new_region);
} else {
return allocate_for_collector(req, in_new_region);
}
}
HeapWord* ShenandoahSerialAllocator::allocate_for_mutator(ShenandoahAllocRequest& req, bool& in_new_region) {
update_allocation_bias();
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
if (partitions.is_empty(ShenandoahFreeSetPartitionId::Mutator)) {
return nullptr;
}
if (partitions.alloc_from_left_bias(ShenandoahFreeSetPartitionId::Mutator)) {
ShenandoahLeftRightIterator iterator(&partitions, ShenandoahFreeSetPartitionId::Mutator);
return allocate_from_regions(iterator, req, in_new_region);
}
ShenandoahRightLeftIterator iterator(&partitions, ShenandoahFreeSetPartitionId::Mutator);
return allocate_from_regions(iterator, req, in_new_region);
}
// Alternating allocation direction between GC passes improves evacuation performance by
// consuming partially-used regions before they become uncollectable floating garbage.
// We bias toward the side with fewer non-empty regions to pack allocations tightly.
void ShenandoahSerialAllocator::update_allocation_bias() {
if (_alloc_bias_weight-- <= 0) {
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
idx_t non_empty_on_left = (partitions.leftmost_empty(ShenandoahFreeSetPartitionId::Mutator)
- partitions.leftmost(ShenandoahFreeSetPartitionId::Mutator));
idx_t non_empty_on_right = (partitions.rightmost(ShenandoahFreeSetPartitionId::Mutator)
- partitions.rightmost_empty(ShenandoahFreeSetPartitionId::Mutator));
partitions.set_bias_from_left_to_right(ShenandoahFreeSetPartitionId::Mutator, (non_empty_on_right < non_empty_on_left));
_alloc_bias_weight = INITIAL_ALLOC_BIAS_WEIGHT;
}
}
template<typename Iter>
HeapWord* ShenandoahSerialAllocator::allocate_from_regions(Iter& iterator, ShenandoahAllocRequest& req, bool& in_new_region) {
for (idx_t idx = iterator.current(); iterator.has_next(); idx = iterator.next()) {
ShenandoahHeapRegion* r = _heap->get_region(idx);
size_t min_size = req.is_lab_alloc() ? req.min_size() : req.size();
if (_free_set->alloc_capacity(r) >= min_size * HeapWordSize) {
HeapWord* result = try_allocate_in(r, req, in_new_region);
if (result != nullptr) {
return result;
}
}
}
return nullptr;
}
// Collector allocation: first try the reserved Collector/OldCollector partition,
// preferring regions with matching affiliation. If that fails and ShenandoahEvacReserveOverflow
// is enabled, steal an empty region from the Mutator partition.
HeapWord* ShenandoahSerialAllocator::allocate_for_collector(ShenandoahAllocRequest& req, bool& in_new_region) {
shenandoah_assert_heaplocked();
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
ShenandoahFreeSetPartitionId which_partition = req.is_old() ? ShenandoahFreeSetPartitionId::OldCollector
: ShenandoahFreeSetPartitionId::Collector;
HeapWord* result = nullptr;
if (partitions.alloc_from_left_bias(which_partition)) {
ShenandoahLeftRightIterator iterator(&partitions, which_partition);
result = allocate_with_affiliation(iterator, req.affiliation(), req, in_new_region);
} else {
ShenandoahRightLeftIterator iterator(&partitions, which_partition);
result = allocate_with_affiliation(iterator, req.affiliation(), req, in_new_region);
}
if (result != nullptr) {
return result;
}
if (!ShenandoahEvacReserveOverflow) {
return nullptr;
}
// Overflow: steal empty region from mutator partition for collector use.
if (partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::Mutator) > 0) {
result = try_allocate_from_mutator(req, in_new_region);
}
return result;
}
template<typename Iter>
HeapWord* ShenandoahSerialAllocator::allocate_with_affiliation(Iter& iterator,
ShenandoahAffiliation affiliation,
ShenandoahAllocRequest& req,
bool& in_new_region) {
assert(affiliation != ShenandoahAffiliation::FREE, "Must not");
ShenandoahHeapRegion* free_region = nullptr;
for (idx_t idx = iterator.current(); iterator.has_next(); idx = iterator.next()) {
ShenandoahHeapRegion* r = _heap->get_region(idx);
if (r->affiliation() == affiliation) {
HeapWord* result = try_allocate_in(r, req, in_new_region);
if (result != nullptr) {
return result;
}
} else if (free_region == nullptr && r->affiliation() == FREE) {
free_region = r;
}
}
if (free_region != nullptr) {
HeapWord* result = try_allocate_in(free_region, req, in_new_region);
assert(result != nullptr, "Allocate in free region in the partition always succeed.");
return result;
}
log_debug(gc, free)("Could not allocate collector region with affiliation: %s for request " PTR_FORMAT,
shenandoah_affiliation_name(affiliation), p2i(&req));
return nullptr;
}
// Flip an empty region from Mutator to Collector/OldCollector partition, then allocate in it.
// Searches from right to left to keep longer-lived collector regions at high addresses.
HeapWord* ShenandoahSerialAllocator::try_allocate_from_mutator(ShenandoahAllocRequest& req, bool& in_new_region) {
ShenandoahRegionPartitions& partitions = _free_set->_partitions;
ShenandoahRightLeftIterator iterator(&partitions, ShenandoahFreeSetPartitionId::Mutator, true);
for (idx_t idx = iterator.current(); iterator.has_next(); idx = iterator.next()) {
ShenandoahHeapRegion* r = _heap->get_region(idx);
if (_free_set->can_allocate_from(r)) {
if (req.is_old()) {
if (!_free_set->flip_to_old_gc(r)) {
continue;
}
} else {
_free_set->flip_to_gc(r);
}
log_debug(gc, free)("Flipped region %zu to gc for request: " PTR_FORMAT, idx, p2i(&req));
return try_allocate_in(r, req, in_new_region);
}
}
return nullptr;
}
// Allocate within region r for the given request. This handles:
// 1. Region recycling and affiliation setup for new (empty) regions
// 2. LAB sizing (TLAB/GCLAB shrink-to-fit, PLAB card-alignment)
// 3. Partition accounting (used, empty counts, retirement)
HeapWord* ShenandoahSerialAllocator::try_allocate_in(ShenandoahHeapRegion* r, ShenandoahAllocRequest& req, bool& in_new_region) {
assert(_free_set->has_alloc_capacity(r), "Performance: should avoid full regions on this path: %zu", r->index());
ShenandoahHeap* heap = ShenandoahHeap::heap();
// Trash regions cannot be used while weak roots processing needs accurate marking info.
if (heap->is_concurrent_weak_root_in_progress() && r->is_trash()) {
return nullptr;
}
HeapWord* result = nullptr;
r->try_recycle_under_lock();
in_new_region = r->is_empty();
if (in_new_region) {
log_debug(gc, free)("Using new region (%zu) for %s (" PTR_FORMAT ").",
r->index(), req.type_string(), p2i(&req));
assert(!r->is_affiliated(), "New region %zu should be unaffiliated", r->index());
r->set_affiliation(req.affiliation());
if (r->is_old()) {
r->end_preemptible_coalesce_and_fill();
}
#ifdef ASSERT
ShenandoahMarkingContext* const ctx = heap->marking_context();
assert(ctx->top_at_mark_start(r) == r->bottom(), "Newly established allocation region starts with TAMS equal to bottom");
assert(ctx->is_bitmap_range_within_region_clear(ctx->top_bitmap(r), r->end()), "Bitmap above top_bitmap() must be clear");
#endif
} else {
assert(r->is_affiliated(), "Region %zu that is not new should be affiliated", r->index());
if (r->affiliation() != req.affiliation()) {
assert(heap->mode()->is_generational(), "Request for %s from %s region should only happen in generational mode.",
req.affiliation_name(), r->affiliation_name());
return nullptr;
}
}
// Perform the actual allocation: LABs may be shrunk to fit, PLABs must be card-aligned.
if (req.is_lab_alloc()) {
size_t adjusted_size = req.size();
size_t free = r->free();
if (req.is_old()) {
assert(heap->mode()->is_generational(), "PLABs are only for generational mode");
assert(_free_set->_partitions.in_free_set(ShenandoahFreeSetPartitionId::OldCollector, r->index()),
"PLABS must be allocated in old_collector_free regions");
size_t usable_free = _free_set->get_usable_free_words(free);
if (adjusted_size > usable_free) {
adjusted_size = usable_free;
}
adjusted_size = align_down(adjusted_size, CardTable::card_size_in_words());
if (adjusted_size >= req.min_size()) {
result = _free_set->allocate_aligned_plab(adjusted_size, req, r);
assert(result != nullptr, "allocate must succeed");
req.set_actual_size(adjusted_size);
} else {
log_trace(gc, free)("Failed to shrink PLAB request (%zu) in region %zu to %zu"
" because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
}
} else {
free = align_down(free >> LogHeapWordSize, MinObjAlignment);
if (adjusted_size > free) {
adjusted_size = free;
}
if (adjusted_size >= req.min_size()) {
result = r->allocate(adjusted_size, req);
assert(result != nullptr, "Allocation must succeed: free %zu, actual %zu", free, adjusted_size);
req.set_actual_size(adjusted_size);
} else {
log_trace(gc, free)("Failed to shrink TLAB or GCLAB request (%zu) in region %zu to %zu"
" because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
}
}
} else {
size_t size = req.size();
result = r->allocate(size, req);
if (result != nullptr) {
req.set_actual_size(size);
}
}
// Update partition used bytes on success.
if (result != nullptr) {
if (req.is_mutator_alloc()) {
assert(req.is_young(), "Mutator allocations always come from young generation.");
_free_set->_partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, req.actual_size() * HeapWordSize);
} else {
assert(req.is_gc_alloc(), "Should be gc_alloc since req wasn't mutator alloc");
// GC allocations set update_watermark so relocated objects aren't re-updated during update-refs.
r->set_update_watermark(r->top());
if (r->is_old()) {
_free_set->_partitions.increase_used(ShenandoahFreeSetPartitionId::OldCollector, (req.actual_size() + req.waste()) * HeapWordSize);
} else {
_free_set->_partitions.increase_used(ShenandoahFreeSetPartitionId::Collector, (req.actual_size() + req.waste()) * HeapWordSize);
}
}
}
ShenandoahFreeSetPartitionId orig_partition;
if (req.is_mutator_alloc()) {
orig_partition = ShenandoahFreeSetPartitionId::Mutator;
return _mutator_alloc.allocate(req, in_new_region);
} else if (req.is_old()) {
orig_partition = ShenandoahFreeSetPartitionId::OldCollector;
return _old_collector_alloc.allocate(req, in_new_region);
} else {
orig_partition = ShenandoahFreeSetPartitionId::Collector;
return _collector_alloc.allocate(req, in_new_region);
}
DEBUG_ONLY(bool boundary_changed = false;)
if ((result != nullptr) && in_new_region) {
_free_set->_partitions.one_region_is_no_longer_empty(orig_partition);
DEBUG_ONLY(boundary_changed = true;)
}
// Retire the region if remaining capacity is too small for any future PLAB.
if (_free_set->alloc_capacity(r) < PLAB::min_size() * HeapWordSize) {
size_t idx = r->index();
size_t waste_bytes = _free_set->_partitions.retire_from_partition(orig_partition, idx, r->used());
DEBUG_ONLY(boundary_changed = true;)
if (req.is_mutator_alloc() && (waste_bytes > 0)) {
req.set_waste(waste_bytes / HeapWordSize);
}
}
// Recompute generation used/affiliated totals.
_free_set->notify_allocation(orig_partition, in_new_region);
#ifdef ASSERT
if (boundary_changed) {
_free_set->_partitions.assert_bounds();
} else {
_free_set->_partitions.assert_bounds_sanity();
}
#endif
return result;
}
void ShenandoahSerialAllocator::clear_retained_regions() {
_mutator_alloc.clear_retained_regions();
_collector_alloc.clear_retained_regions();
_old_collector_alloc.clear_retained_regions();
}

View File

@ -25,58 +25,27 @@
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHSERIALALLOCATOR_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHSERIALALLOCATOR_HPP
#include "gc/shenandoah/shenandoahAffiliation.hpp"
#include "gc/shenandoah/shenandoahAllocator.hpp"
class ShenandoahFreeSet;
class ShenandoahHeap;
class ShenandoahHeapRegion;
class ShenandoahRegionPartitions;
#include "gc/shenandoah/shenandoahPartitionAllocator.hpp"
// ShenandoahSerialAllocator performs all allocations serially under the heap lock.
// It selects regions from the partitioned free set:
// - Mutator allocations: from Mutator partition with left/right bias alternation
// - Collector allocations: from Collector/OldCollector partition with affiliation preference
// - Overflow: collector may steal empty regions from Mutator partition
// It dispatches requests to per-partition allocators based on request type:
// - Mutator allocations: routed to the Mutator partition allocator
// - Collector allocations: routed to Collector or OldCollector partition allocator
// - Humongous: delegated to ShenandoahFreeSet::allocate_contiguous()
class ShenandoahSerialAllocator : public ShenandoahAllocator {
private:
ShenandoahHeap* const _heap;
// Allocation direction alternates to avoid repeatedly skipping the same uncollected regions.
ssize_t _alloc_bias_weight;
static const ssize_t INITIAL_ALLOC_BIAS_WEIGHT = 256;
// Attempt allocation within a single region. Handles LAB sizing, PLAB card-alignment,
// affiliation checks, and updates partition accounting via ShenandoahFreeSet.
// Retires the region if remaining capacity falls below PLAB::min_size().
HeapWord* try_allocate_in(ShenandoahHeapRegion* r, ShenandoahAllocRequest& req, bool& in_new_region);
HeapWord* allocate_single(ShenandoahAllocRequest& req, bool& in_new_region);
HeapWord* allocate_for_mutator(ShenandoahAllocRequest& req, bool& in_new_region);
HeapWord* allocate_for_collector(ShenandoahAllocRequest& req, bool& in_new_region);
// Steal an empty region from Mutator partition for collector use.
HeapWord* try_allocate_from_mutator(ShenandoahAllocRequest& req, bool& in_new_region);
// Re-evaluate left-to-right vs right-to-left bias for mutator allocations.
void update_allocation_bias();
// Scan regions using the given iterator, attempt allocation in each.
template<typename Iter>
HeapWord* allocate_from_regions(Iter& iterator, ShenandoahAllocRequest& req, bool& in_new_region);
// For collector: prefer regions matching the requested affiliation, fall back to FREE regions.
template<typename Iter>
HeapWord* allocate_with_affiliation(Iter& iterator,
ShenandoahAffiliation affiliation,
ShenandoahAllocRequest& req,
bool& in_new_region);
ShenandoahPartitionAllocator<ShenandoahFreeSetPartitionId::Mutator> _mutator_alloc;
ShenandoahPartitionAllocator<ShenandoahFreeSetPartitionId::Collector> _collector_alloc;
ShenandoahPartitionAllocator<ShenandoahFreeSetPartitionId::OldCollector> _old_collector_alloc;
public:
ShenandoahSerialAllocator(ShenandoahFreeSet* free_set);
HeapWord* allocate(ShenandoahAllocRequest& req, bool& in_new_region) override;
// Called during free-set rebuild to invalidate retained regions.
void clear_retained_regions() override;
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHSERIALALLOCATOR_HPP