/* * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. *
*/
// Determines how many mutator threads can process the buffers in parallel.
uint G1DirtyCardQueueSet::num_par_ids() { return (uint)os::initial_active_processor_count();
}
void G1DirtyCardQueueSet::enqueue_completed_buffer(BufferNode* cbn) {
assert(cbn != NULL, "precondition"); // Increment _num_cards before adding to queue, so queue removal doesn't // need to deal with _num_cards possibly going negative.
Atomic::add(&_num_cards, buffer_size() - cbn->index()); // Perform push in CS. The old tail may be popped while the push is // observing it (attaching it to the new buffer). We need to ensure it // can't be reused until the push completes, to avoid ABA problems.
GlobalCounter::CriticalSection cs(Thread::current());
_completed.push(*cbn);
}
// Thread-safe attempt to remove and return the first buffer from // the _completed queue, using the NonblockingQueue::try_pop() underneath. // It has a limitation that it may return NULL when there are objects // in the queue if there is a concurrent push/append operation.
BufferNode* G1DirtyCardQueueSet::dequeue_completed_buffer() {
Thread* current_thread = Thread::current();
BufferNode* result = NULL; while (true) { // Use GlobalCounter critical section to avoid ABA problem. // The release of a buffer to its allocator's free list uses // GlobalCounter::write_synchronize() to coordinate with this // dequeuing operation. // We use a CS per iteration, rather than over the whole loop, // because we're not guaranteed to make progress. Lingering in // one CS could defer releasing buffer to the free list for reuse, // leading to excessive allocations.
GlobalCounter::CriticalSection cs(current_thread); if (_completed.try_pop(&result)) return result;
}
}
BufferNode* G1DirtyCardQueueSet::get_completed_buffer() {
BufferNode* result = dequeue_completed_buffer(); if (result == NULL) { // Unlikely if no paused buffers.
enqueue_previous_paused_buffers();
result = dequeue_completed_buffer(); if (result == NULL) return NULL;
}
Atomic::sub(&_num_cards, buffer_size() - result->index()); return result;
}
#ifdef ASSERT void G1DirtyCardQueueSet::verify_num_cards() const {
size_t actual = 0; for (BufferNode* cur = _completed.first();
!_completed.is_end(cur);
cur = cur->next()) {
actual += buffer_size() - cur->index();
}
assert(actual == Atomic::load(&_num_cards), "Num entries in completed buffers should be " SIZE_FORMAT " but are " SIZE_FORMAT,
Atomic::load(&_num_cards), actual);
} #endif// ASSERT
void G1DirtyCardQueueSet::PausedBuffers::add(BufferNode* node) {
assert_not_at_safepoint();
PausedList* plist = Atomic::load_acquire(&_plist); if (plist == NULL) { // Try to install a new next list.
plist = new PausedList();
PausedList* old_plist = Atomic::cmpxchg(&_plist, (PausedList*)NULL, plist); if (old_plist != NULL) { // Some other thread installed a new next list. Use it instead. delete plist;
plist = old_plist;
}
}
assert(plist->is_next(), "invariant");
plist->add(node);
}
G1DirtyCardQueueSet::HeadTail G1DirtyCardQueueSet::PausedBuffers::take_previous() {
assert_not_at_safepoint();
PausedList* previous;
{ // Deal with plist in a critical section, to prevent it from being // deleted out from under us by a concurrent take_previous().
GlobalCounter::CriticalSection cs(Thread::current());
previous = Atomic::load_acquire(&_plist); if ((previous == NULL) || // Nothing to take.
previous->is_next() || // Not from a previous safepoint. // Some other thread stole it.
(Atomic::cmpxchg(&_plist, previous, (PausedList*)NULL) != previous)) { return HeadTail();
}
} // We now own previous.
HeadTail result = previous->take(); // There might be other threads examining previous (in concurrent // take_previous()). Synchronize to wait until any such threads are // done with such examination before deleting.
GlobalCounter::write_synchronize(); delete previous; return result;
}
void G1DirtyCardQueueSet::record_paused_buffer(BufferNode* node) {
assert_not_at_safepoint();
assert(node->next() == NULL, "precondition"); // Ensure there aren't any paused buffers from a previous safepoint.
enqueue_previous_paused_buffers(); // Cards for paused buffers are included in count, to contribute to // notification checking after the coming safepoint if it doesn't GC. // Note that this means the queue's _num_cards differs from the number // of cards in the queued buffers when there are paused buffers.
Atomic::add(&_num_cards, buffer_size() - node->index());
_paused.add(node);
}
void G1DirtyCardQueueSet::enqueue_paused_buffers_aux(const HeadTail& paused) { if (paused._head != NULL) {
assert(paused._tail != NULL, "invariant"); // Cards from paused buffers are already recorded in the queue count.
_completed.append(*paused._head, *paused._tail);
}
}
// Merge lists of buffers. The source queue set is emptied as a // result. The queue sets must share the same allocator. void G1DirtyCardQueueSet::merge_bufferlists(G1RedirtyCardsQueueSet* src) {
assert(allocator() == src->allocator(), "precondition"); const BufferNodeList from = src->take_all_completed_buffers(); if (from._head != NULL) {
Atomic::add(&_num_cards, from._entry_count);
_completed.append(*from._head, *from._tail);
}
}
// Sorts the cards from start_index to _node_buffer_size in *decreasing* // address order. Tests showed that this order is preferable to not sorting // or increasing address order. void sort_cards(size_t start_index) {
QuickSort::sort(&_node_buffer[start_index],
_node_buffer_size - start_index,
compare_card, false);
}
// Returns the index to the first clean card in the buffer.
size_t clean_cards() { const size_t start = _node->index();
assert(start <= _node_buffer_size, "invariant");
// Two-fingered compaction algorithm similar to the filtering mechanism in // SATBMarkQueue. The main difference is that clean_card_before_refine() // could change the buffer element in-place. // We don't check for SuspendibleThreadSet::should_yield(), because // cleaning and redirtying the cards is fast.
CardTable::CardValue** src = &_node_buffer[start];
CardTable::CardValue** dst = &_node_buffer[_node_buffer_size];
assert(src <= dst, "invariant"); for ( ; src < dst; ++src) { // Search low to high for a card to keep. if (_g1rs->clean_card_before_refine(src)) { // Found keeper. Search high to low for a card to discard. while (src < --dst) { if (!_g1rs->clean_card_before_refine(dst)) {
*dst = *src; // Replace discard with keeper. break;
}
} // If discard search failed (src == dst), the outer loop will also end.
}
}
// dst points to the first retained clean card, or the end of the buffer // if all the cards were discarded. const size_t first_clean = dst - _node_buffer;
assert(first_clean >= start && first_clean <= _node_buffer_size, "invariant"); // Discarded cards are considered as refined.
_stats->inc_refined_cards(first_clean - start);
_stats->inc_precleaned_cards(first_clean - start); return first_clean;
}
bool refine_cleaned_cards(size_t start_index) { bool result = true;
size_t i = start_index; for ( ; i < _node_buffer_size; ++i) { if (SuspendibleThreadSet::should_yield()) {
redirty_unrefined_cards(i);
result = false; break;
}
_g1rs->refine_card_concurrently(_node_buffer[i], _worker_id);
}
_node->set_index(i);
_stats->inc_refined_cards(i - start_index); return result;
}
bool refine() {
size_t first_clean_index = clean_cards(); if (first_clean_index == _node_buffer_size) {
_node->set_index(first_clean_index); returntrue;
} // This fence serves two purposes. First, the cards must be cleaned // before processing the contents. Second, we can't proceed with // processing a region until after the read of the region's top in // collect_and_clean_cards(), for synchronization with possibly concurrent // humongous object allocation (see comment at the StoreStore fence before // setting the regions' tops in humongous allocation path). // It's okay that reading region's top and reading region's type were racy // wrto each other. We need both set, in any order, to proceed.
OrderAccess::fence();
sort_cards(first_clean_index); return refine_cleaned_cards(first_clean_index);
}
};
// No need for mutator refinement if number of cards is below limit. if (Atomic::load(&_num_cards) <= Atomic::load(&_mutator_refinement_threshold)) { return;
}
// Don't try to process a buffer that will just get immediately paused. // When going into a safepoint it's just a waste of effort. // When coming out of a safepoint, Java threads may be running before the // yield request (for non-Java threads) has been cleared. if (SuspendibleThreadSet::should_yield()) { return;
}
// Only Java threads perform mutator refinement. if (!Thread::current()->is_Java_thread()) { return;
}
BufferNode* node = get_completed_buffer(); if (node == NULL) return; // Didn't get a buffer to process.
// Refine cards in buffer.
uint worker_id = _free_ids.claim_par_id(); // temporarily claim an id bool fully_processed = refine_buffer(node, worker_id, stats);
_free_ids.release_par_id(worker_id); // release the id
// Deal with buffer after releasing id, to let another thread use id.
handle_refined_buffer(node, fully_processed);
}
bool G1DirtyCardQueueSet::refine_completed_buffer_concurrently(uint worker_id,
size_t stop_at,
G1ConcurrentRefineStats* stats) { // Not enough cards to trigger processing. if (Atomic::load(&_num_cards) <= stop_at) returnfalse;
BufferNode* node = get_completed_buffer(); if (node == NULL) returnfalse; // Didn't get a buffer to process.
// Disable mutator refinement until concurrent refinement decides otherwise.
set_mutator_refinement_threshold(SIZE_MAX);
// Iterate over all the threads, if we find a partial log add it to // the global list of logs. struct ConcatenateThreadLogClosure : public ThreadClosure {
G1DirtyCardQueueSet& _qset;
G1ConcurrentRefineStats _total_stats;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.