8202017: Merge Reference Enqueuing phase with phase 3 of Reference processing

Do reference enqueuing work directly in phase 3 after every Reference.

Reviewed-by: kbarrett, sangheki
This commit is contained in:
Thomas Schatzl 2018-05-08 10:01:29 +02:00
parent 59d4a62f42
commit 035d0190a3
18 changed files with 48 additions and 280 deletions

View File

@ -5254,16 +5254,7 @@ void CMSCollector::refProcessingWork() {
restore_preserved_marks_if_any(); // done single-threaded for now
rp->set_enqueuing_is_done(true);
if (rp->processing_is_mt()) {
rp->balance_all_queues();
CMSRefProcTaskExecutor task_executor(*this);
rp->enqueue_discovered_references(&task_executor, &pt);
} else {
rp->enqueue_discovered_references(NULL, &pt);
}
rp->verify_no_references_recorded();
pt.print_enqueue_phase();
assert(!rp->discovery_enabled(), "should have been disabled");
}
#ifndef PRODUCT

View File

@ -1054,18 +1054,10 @@ void ParNewGeneration::collect(bool full,
update_time_of_last_gc(now);
rp->set_enqueuing_is_done(true);
if (rp->processing_is_mt()) {
ParNewRefProcTaskExecutor task_executor(*this, *_old_gen, thread_state_set);
rp->enqueue_discovered_references(&task_executor, &pt);
} else {
rp->enqueue_discovered_references(NULL, &pt);
}
rp->verify_no_references_recorded();
gch->trace_heap_after_gc(gc_tracer());
pt.print_enqueue_phase();
_gc_timer->register_gc_end();
_gc_tracer.report_gc_end(_gc_timer->gc_end(), _gc_timer->time_partitions());

View File

@ -3879,7 +3879,6 @@ void G1STWRefProcTaskExecutor::execute(EnqueueTask& enq_task) {
// End of weak reference support closures
// Weak Reference processing during an evacuation pause (part 1).
void G1CollectedHeap::process_discovered_references(G1ParScanThreadStateSet* per_thread_states) {
double ref_proc_start = os::elapsedTime();
@ -3939,42 +3938,15 @@ void G1CollectedHeap::process_discovered_references(G1ParScanThreadStateSet* per
// We have completed copying any necessary live referent objects.
assert(pss->queue_is_empty(), "both queue and overflow should be empty");
make_pending_list_reachable();
rp->verify_no_references_recorded();
double ref_proc_time = os::elapsedTime() - ref_proc_start;
g1_policy()->phase_times()->record_ref_proc_time(ref_proc_time * 1000.0);
}
// Weak Reference processing during an evacuation pause (part 2).
void G1CollectedHeap::enqueue_discovered_references(G1ParScanThreadStateSet* per_thread_states) {
double ref_enq_start = os::elapsedTime();
ReferenceProcessor* rp = _ref_processor_stw;
assert(!rp->discovery_enabled(), "should have been disabled as part of processing");
ReferenceProcessorPhaseTimes* pt = g1_policy()->phase_times()->ref_phase_times();
// Now enqueue any remaining on the discovered lists on to
// the pending list.
if (!rp->processing_is_mt()) {
// Serial reference processing...
rp->enqueue_discovered_references(NULL, pt);
} else {
// Parallel reference enqueueing
uint n_workers = workers()->active_workers();
assert(n_workers <= rp->max_num_queues(),
"Mismatch between the number of GC workers %u and the maximum number of Reference process queues %u",
n_workers, rp->max_num_queues());
G1STWRefProcTaskExecutor par_task_executor(this, per_thread_states, workers(), _task_queues, n_workers);
rp->enqueue_discovered_references(&par_task_executor, pt);
}
rp->verify_no_references_recorded();
assert(!rp->discovery_enabled(), "should have been disabled");
// If during an initial mark pause we install a pending list head which is not otherwise reachable
// ensure that it is marked in the bitmap for concurrent marking to discover.
void G1CollectedHeap::make_pending_list_reachable() {
if (collector_state()->in_initial_mark_gc()) {
oop pll_head = Universe::reference_pending_list();
if (pll_head != NULL) {
@ -3982,14 +3954,6 @@ void G1CollectedHeap::enqueue_discovered_references(G1ParScanThreadStateSet* per
_cm->mark_in_next_bitmap(0 /* worker_id */, pll_head);
}
}
// FIXME
// CM's reference processing also cleans up the string and symbol tables.
// Should we do that here also? We could, but it is a serial operation
// and could significantly increase the pause time.
double ref_enq_time = os::elapsedTime() - ref_enq_start;
g1_policy()->phase_times()->record_ref_enq_time(ref_enq_time * 1000.0);
}
void G1CollectedHeap::merge_per_thread_state_info(G1ParScanThreadStateSet* per_thread_states) {
@ -4069,7 +4033,11 @@ void G1CollectedHeap::post_evacuate_collection_set(EvacuationInfo& evacuation_in
// objects (and their reachable sub-graphs) that were
// not copied during the pause.
process_discovered_references(per_thread_states);
enqueue_discovered_references(per_thread_states);
// FIXME
// CM's reference processing also cleans up the string and symbol tables.
// Should we do that here also? We could, but it is a serial operation
// and could significantly increase the pause time.
G1STWIsAliveClosure is_alive(this);
G1KeepAliveClosure keep_alive(this);

View File

@ -513,13 +513,13 @@ private:
// allocated block, or else "NULL".
HeapWord* expand_and_allocate(size_t word_size);
// Process any reference objects discovered during
// an incremental evacuation pause.
// Process any reference objects discovered.
void process_discovered_references(G1ParScanThreadStateSet* per_thread_states);
// Enqueue any remaining discovered references
// after processing.
void enqueue_discovered_references(G1ParScanThreadStateSet* per_thread_states);
// If during an initial mark pause we may install a pending list head which is not
// otherwise reachable ensure that it is marked in the bitmap for concurrent marking
// to discover.
void make_pending_list_reachable();
// Merges the information gathered on a per-thread basis for all worker threads
// during GC into global variables.

View File

@ -1677,12 +1677,7 @@ void G1ConcurrentMark::weak_refs_work(bool clear_all_soft_refs) {
assert(rp->num_queues() == active_workers, "why not");
rp->enqueue_discovered_references(executor, &pt);
rp->verify_no_references_recorded();
pt.print_enqueue_phase();
assert(!rp->discovery_enabled(), "Post condition");
}

View File

@ -108,8 +108,4 @@ void G1FullGCReferenceProcessingExecutor::execute(STWGCTimer* timer, G1FullGCTra
pt.print_all_references();
assert(marker->oop_stack()->is_empty(), "Should be no oops on the stack");
// Now enqueue the references.
_reference_processor->enqueue_discovered_references(executor, &pt);
pt.print_enqueue_phase();
}

View File

@ -129,7 +129,6 @@ void G1GCPhaseTimes::reset() {
_cur_clear_ct_time_ms = 0.0;
_cur_expand_heap_time_ms = 0.0;
_cur_ref_proc_time_ms = 0.0;
_cur_ref_enq_time_ms = 0.0;
_cur_weak_ref_proc_time_ms = 0.0;
_cur_collection_start_sec = 0.0;
_root_region_scan_wait_time_ms = 0.0;
@ -383,7 +382,6 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set() const {
_recorded_preserve_cm_referents_time_ms +
_cur_ref_proc_time_ms +
_cur_weak_ref_proc_time_ms +
_cur_ref_enq_time_ms +
_cur_clear_ct_time_ms +
_recorded_merge_pss_time_ms +
_cur_strong_code_root_purge_time_ms +
@ -416,9 +414,6 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set() const {
trace_time("Remove Self Forwards",_cur_evac_fail_remove_self_forwards);
}
debug_time_for_reference("Reference Enqueuing", _cur_ref_enq_time_ms);
_ref_phase_times.print_enqueue_phase(2, false);
debug_time("Merge Per-Thread State", _recorded_merge_pss_time_ms);
debug_time("Code Roots Purge", _cur_strong_code_root_purge_time_ms);

View File

@ -261,10 +261,6 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
_cur_weak_ref_proc_time_ms = ms;
}
void record_ref_enq_time(double ms) {
_cur_ref_enq_time_ms = ms;
}
void record_root_region_scan_wait_time(double time_ms) {
_root_region_scan_wait_time_ms = time_ms;
}

View File

@ -260,11 +260,7 @@ bool PSMarkSweep::invoke_no_policy(bool clear_all_softrefs) {
DerivedPointerTable::update_pointers();
#endif
ReferenceProcessorPhaseTimes pt(_gc_timer, ref_processor()->num_queues());
ref_processor()->enqueue_discovered_references(NULL, &pt);
pt.print_enqueue_phase();
assert(!ref_processor()->discovery_enabled(), "Should have been disabled earlier");
// Update time of last GC
reset_millis_since_last_gc();

View File

@ -1038,12 +1038,6 @@ void PSParallelCompact::post_compact()
DerivedPointerTable::update_pointers();
#endif
ReferenceProcessorPhaseTimes pt(&_gc_timer, ref_processor()->num_queues());
ref_processor()->enqueue_discovered_references(NULL, &pt);
pt.print_enqueue_phase();
if (ZapUnusedHeapArea) {
heap->gen_mangle_unused_area();
}

View File

@ -430,16 +430,6 @@ bool PSScavenge::invoke_no_policy() {
_gc_tracer.report_gc_reference_stats(stats);
pt.print_all_references();
// Enqueue reference objects discovered during scavenge.
if (reference_processor()->processing_is_mt()) {
PSRefProcTaskExecutor task_executor;
reference_processor()->enqueue_discovered_references(&task_executor, &pt);
} else {
reference_processor()->enqueue_discovered_references(NULL, &pt);
}
pt.print_enqueue_phase();
}
assert(promotion_manager->stacks_empty(),"stacks should be empty at this point");

View File

@ -517,9 +517,7 @@ void GenCollectedHeap::collect_generation(Generation* gen, bool full, size_t siz
}
gen->collect(full, clear_soft_refs, size, is_tlab);
if (!rp->enqueuing_is_done()) {
ReferenceProcessorPhaseTimes pt(NULL, rp->num_queues());
rp->enqueue_discovered_references(NULL, &pt);
pt.print_enqueue_phase();
rp->disable_discovery();
} else {
rp->set_enqueuing_is_done(false);
}

View File

@ -257,113 +257,6 @@ ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
return stats;
}
void ReferenceProcessor::enqueue_discovered_references(AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times) {
// Enqueue references that are not made active again, and
// clear the decks for the next collection (cycle).
enqueue_discovered_reflists(task_executor, phase_times);
// Stop treating discovered references specially.
disable_discovery();
}
void ReferenceProcessor::enqueue_discovered_reflist(DiscoveredList& refs_list) {
// Given a list of refs linked through the "discovered" field
// (java.lang.ref.Reference.discovered), self-loop their "next" field
// thus distinguishing them from active References, then
// prepend them to the pending list.
//
// The Java threads will see the Reference objects linked together through
// the discovered field. Instead of trying to do the write barrier updates
// in all places in the reference processor where we manipulate the discovered
// field we make sure to do the barrier here where we anyway iterate through
// all linked Reference objects. Note that it is important to not dirty any
// cards during reference processing since this will cause card table
// verification to fail for G1.
log_develop_trace(gc, ref)("ReferenceProcessor::enqueue_discovered_reflist list " INTPTR_FORMAT, p2i(&refs_list));
oop obj = NULL;
oop next_discovered = refs_list.head();
// Walk down the list, self-looping the next field
// so that the References are not considered active.
while (obj != next_discovered) {
obj = next_discovered;
assert(obj->is_instance(), "should be an instance object");
assert(InstanceKlass::cast(obj->klass())->is_reference_instance_klass(), "should be reference object");
next_discovered = java_lang_ref_Reference::discovered(obj);
log_develop_trace(gc, ref)(" obj " INTPTR_FORMAT "/next_discovered " INTPTR_FORMAT, p2i(obj), p2i(next_discovered));
assert(java_lang_ref_Reference::next(obj) == NULL,
"Reference not active; should not be discovered");
// Self-loop next, so as to make Ref not active.
java_lang_ref_Reference::set_next_raw(obj, obj);
if (next_discovered != obj) {
HeapAccess<AS_NO_KEEPALIVE>::oop_store_at(obj, java_lang_ref_Reference::discovered_offset, next_discovered);
} else {
// This is the last object.
// Swap refs_list into pending list and set obj's
// discovered to what we read from the pending list.
oop old = Universe::swap_reference_pending_list(refs_list.head());
HeapAccess<AS_NO_KEEPALIVE>::oop_store_at(obj, java_lang_ref_Reference::discovered_offset, old);
}
}
}
// Parallel enqueue task
class RefProcEnqueueTask: public AbstractRefProcTaskExecutor::EnqueueTask {
public:
RefProcEnqueueTask(ReferenceProcessor& ref_processor,
DiscoveredList discovered_refs[],
int n_queues,
ReferenceProcessorPhaseTimes* phase_times)
: EnqueueTask(ref_processor, discovered_refs, n_queues, phase_times)
{ }
virtual void work(unsigned int work_id) {
RefProcWorkerTimeTracker tt(ReferenceProcessorPhaseTimes::RefEnqueue, _phase_times, work_id);
assert(work_id < (unsigned int)_ref_processor.max_num_queues(), "Index out-of-bounds");
// Simplest first cut: static partitioning.
int index = work_id;
// The increment on "index" must correspond to the maximum number of queues
// (n_queues) with which that ReferenceProcessor was created. That
// is because of the "clever" way the discovered references lists were
// allocated and are indexed into.
assert(_n_queues == (int) _ref_processor.max_num_queues(), "Different number not expected");
for (int j = 0;
j < ReferenceProcessor::number_of_subclasses_of_ref();
j++, index += _n_queues) {
_ref_processor.enqueue_discovered_reflist(_refs_lists[index]);
_refs_lists[index].set_head(NULL);
_refs_lists[index].set_length(0);
}
}
};
// Enqueue references that are not made active again
void ReferenceProcessor::enqueue_discovered_reflists(AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times) {
ReferenceProcessorStats stats(total_count(_discoveredSoftRefs),
total_count(_discoveredWeakRefs),
total_count(_discoveredFinalRefs),
total_count(_discoveredPhantomRefs));
RefProcEnqueueTimeTracker tt(phase_times, stats);
if (_processing_is_mt && task_executor != NULL) {
// Parallel code
RefProcEnqueueTask tsk(*this, _discovered_refs, _max_num_queues, phase_times);
task_executor->execute(tsk);
} else {
// Serial code: call the parent class's implementation
for (uint i = 0; i < _max_num_queues * number_of_subclasses_of_ref(); i++) {
enqueue_discovered_reflist(_discovered_refs[i]);
_discovered_refs[i].set_head(NULL);
_discovered_refs[i].set_length(0);
}
}
}
void DiscoveredListIterator::load_ptrs(DEBUG_ONLY(bool allow_null_referent)) {
_current_discovered_addr = java_lang_ref_Reference::discovered_addr_raw(_current_discovered);
oop discovered = java_lang_ref_Reference::discovered(_current_discovered);
@ -409,6 +302,25 @@ void DiscoveredListIterator::clear_referent() {
RawAccess<>::oop_store(_referent_addr, oop(NULL));
}
void DiscoveredListIterator::enqueue() {
// Self-loop next, so as to make Ref not active.
java_lang_ref_Reference::set_next_raw(_current_discovered, _current_discovered);
HeapAccess<AS_NO_KEEPALIVE>::oop_store_at(_current_discovered,
java_lang_ref_Reference::discovered_offset,
_next_discovered);
}
void DiscoveredListIterator::complete_enqeue() {
if (_prev_discovered != NULL) {
// This is the last object.
// Swap refs_list into pending list and set obj's
// discovered to what we read from the pending list.
oop old = Universe::swap_reference_pending_list(_refs_list.head());
HeapAccess<AS_NO_KEEPALIVE>::oop_store_at(_prev_discovered, java_lang_ref_Reference::discovered_offset, old);
}
}
// NOTE: process_phase*() are largely similar, and at a high level
// merely iterate over the extant list applying a predicate to
// each of its elements and possibly removing that element from the
@ -556,13 +468,18 @@ void ReferenceProcessor::process_phase3(DiscoveredList& refs_list,
// keep the referent around
iter.make_referent_alive();
}
iter.enqueue();
log_develop_trace(gc, ref)("Adding %sreference (" INTPTR_FORMAT ": %s) as pending",
clear_referent ? "cleared " : "", p2i(iter.obj()), iter.obj()->klass()->internal_name());
assert(oopDesc::is_oop(iter.obj(), UseConcMarkSweepGC), "Adding a bad reference");
iter.next();
}
iter.complete_enqeue();
// Close the reachable set
complete_gc->do_void();
// Clear the list.
refs_list.set_head(NULL);
refs_list.set_length(0);
}
void
@ -785,13 +702,6 @@ void ReferenceProcessor::balance_queues(DiscoveredList ref_lists[])
#endif
}
void ReferenceProcessor::balance_all_queues() {
balance_queues(_discoveredSoftRefs);
balance_queues(_discoveredWeakRefs);
balance_queues(_discoveredFinalRefs);
balance_queues(_discoveredPhantomRefs);
}
void ReferenceProcessor::process_discovered_reflist(
DiscoveredList refs_lists[],
ReferencePolicy* policy,

View File

@ -143,6 +143,12 @@ public:
}
}
// Do enqueuing work, i.e. notifying the GC about the changed discovered pointers.
void enqueue();
// Move enqueued references to the reference pending list.
void complete_enqeue();
// NULL out referent pointer.
void clear_referent();
@ -273,9 +279,6 @@ class ReferenceProcessor : public ReferenceDiscoverer {
OopClosure* keep_alive,
VoidClosure* complete_gc);
// Enqueue references with a certain reachability level
void enqueue_discovered_reflist(DiscoveredList& refs_list);
// "Preclean" all the discovered reference lists
// by removing references with strongly reachable referents.
// The first argument is a predicate on an oop that indicates
@ -295,9 +298,6 @@ class ReferenceProcessor : public ReferenceDiscoverer {
// occupying the i / _num_queues slot.
const char* list_name(uint i);
void enqueue_discovered_reflists(AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times);
// "Preclean" the given discovered reference list
// by removing references with strongly reachable referents.
// Currently used in support of CMS only.
@ -387,8 +387,6 @@ public:
// iterate over oops
void weak_oops_do(OopClosure* f); // weak roots
// Balance each of the discovered lists.
void balance_all_queues();
void verify_list(DiscoveredList& ref_list);
// Discover a Reference object, using appropriate discovery criteria
@ -405,10 +403,6 @@ public:
AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times);
// Enqueue references at end of GC (called by the garbage collector)
void enqueue_discovered_references(AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times);
// If a discovery is in process that is being superceded, abandon it: all
// the discovered lists will be empty, and all the objects on them will
// have NULL discovered fields. Must be called only at a safepoint.

View File

@ -78,8 +78,6 @@ static const char* phase_enum_2_phase_string(ReferenceProcessorPhaseTimes::RefPr
case ReferenceProcessorPhaseTimes::FinalRefPhase3:
case ReferenceProcessorPhaseTimes::PhantomRefPhase3:
return "Phase3";
case ReferenceProcessorPhaseTimes::RefEnqueue:
return "Reference Enqueuing";
default:
ShouldNotReachHere();
return NULL;
@ -191,21 +189,6 @@ RefProcPhaseTimesTracker::~RefProcPhaseTimesTracker() {
times->set_ref_cleared(ref_type, discovered - after_count);
}
RefProcEnqueueTimeTracker::RefProcEnqueueTimeTracker(ReferenceProcessorPhaseTimes* phase_times,
ReferenceProcessorStats& stats) :
RefProcPhaseTimeBaseTracker("Reference Enqueuing", phase_times) {
phase_times->set_ref_enqueued(REF_SOFT, stats.soft_count());
phase_times->set_ref_enqueued(REF_WEAK, stats.weak_count());
phase_times->set_ref_enqueued(REF_FINAL, stats.final_count());
phase_times->set_ref_enqueued(REF_PHANTOM, stats.phantom_count());
}
RefProcEnqueueTimeTracker::~RefProcEnqueueTimeTracker() {
double elapsed = elapsed_time();
phase_times()->set_par_phase_time_ms(ReferenceProcessorPhaseTimes::RefEnqueue, elapsed);
}
ReferenceProcessorPhaseTimes::ReferenceProcessorPhaseTimes(GCTimer* gc_timer, uint max_gc_threads) :
_gc_timer(gc_timer), _processing_is_mt(false) {
@ -369,17 +352,6 @@ ReferenceProcessorPhaseTimes::par_phase(RefProcPhaseNumbers phase_number) const
return (RefProcParPhases)result;
}
void ReferenceProcessorPhaseTimes::print_enqueue_phase(uint base_indent, bool print_total) const {
if (print_total) {
print_phase(RefEnqueue, base_indent);
}
log_debug(gc, phases, ref)("%sReference Counts: Soft: " SIZE_FORMAT " Weak: " SIZE_FORMAT
" Final: " SIZE_FORMAT " Phantom: " SIZE_FORMAT ,
Indents[base_indent + 1], ref_enqueued(REF_SOFT), ref_enqueued(REF_WEAK),
ref_enqueued(REF_FINAL), ref_enqueued(REF_PHANTOM));
}
#define TIME_FORMAT "%.1lfms"
void ReferenceProcessorPhaseTimes::print_all_references(uint base_indent, bool print_total) const {

View File

@ -46,7 +46,6 @@ public:
FinalRefPhase3,
PhantomRefPhase2,
PhantomRefPhase3,
RefEnqueue,
RefParPhaseMax
};
@ -133,7 +132,6 @@ public:
// Reset all fields. If not reset at next cycle, an assertion will fail.
void reset();
void print_enqueue_phase(uint base_indent = 0, bool print_total = true) const;
void print_all_references(uint base_indent = 0, bool print_total = true) const;
};
@ -203,13 +201,4 @@ public:
~RefProcPhaseTimesTracker();
};
// Updates enqueue time related information.
// - Enqueueing time, enqueued reference count and stats for each working thread if MT processed.
class RefProcEnqueueTimeTracker : public RefProcPhaseTimeBaseTracker {
public:
RefProcEnqueueTimeTracker(ReferenceProcessorPhaseTimes* phase_times,
ReferenceProcessorStats& stats);
~RefProcEnqueueTimeTracker();
};
#endif // SHARE_VM_GC_SHARED_REFERENCEPROCESSORPHASETIMES_HPP

View File

@ -141,7 +141,6 @@ public class TestGCLogMessages {
new LogMessageWithLevel("Resize TLABs", Level.DEBUG),
// Reference Processing
new LogMessageWithLevel("Reference Processing", Level.DEBUG),
new LogMessageWithLevel("Reference Enqueuing", Level.DEBUG),
// VM internal reference processing
new LogMessageWithLevel("Weak Processing", Level.DEBUG),

View File

@ -93,9 +93,6 @@ public class TestPrintReferences {
gcLogTimeRegex + indent(8) + "Discovered: " + countRegex + "\n" +
gcLogTimeRegex + indent(8) + "Cleared: " + countRegex + "\n";
String softRefDetailRegex = gcLogTimeRegex + indent(8) + phase1 + ": " + timeRegex + "\n" + refDetailRegex;
String enqueueRegex = gcLogTimeRegex + indent(4) + "Reference Enqueuing: " + timeRegex + "\n";
String enqueueDetailRegex = gcLogTimeRegex + indent(6) + "Reference Counts: Soft: " + countRegex +
" Weak: " + countRegex + " Final: " + countRegex + " Phantom: " + countRegex + "\n";
output.shouldMatch(/* Total Reference processing time */
totalRegex +
@ -106,11 +103,7 @@ public class TestPrintReferences {
/* FinalReference processing */
finalRefRegex + balanceRegex + refDetailRegex +
/* PhantomReference processing */
phantomRefRegex + balanceRegex + refDetailRegex +
/* Total Enqueuing time */
enqueueRegex +
/* Enqueued Stats */
enqueueDetailRegex
phantomRefRegex + balanceRegex + refDetailRegex
);
}