8240556: Abort concurrent mark after effective eager reclamation of humongous objects

Reviewed-by: sjohanss, ayang
This commit is contained in:
Thomas Schatzl 2020-09-25 07:11:55 +00:00
parent dcde95ba0d
commit 37b70282b5
15 changed files with 301 additions and 151 deletions

@ -850,7 +850,7 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
// before the allocation is that we avoid having to keep track of the newly
// allocated memory while we do a GC.
if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
word_size)) {
word_size)) {
collect(GCCause::_g1_humongous_allocation);
}
@ -2717,12 +2717,18 @@ HeapWord* G1CollectedHeap::do_collection_pause(size_t word_size,
return result;
}
void G1CollectedHeap::do_concurrent_mark() {
void G1CollectedHeap::start_concurrent_cycle(bool concurrent_operation_is_full_mark) {
assert(!_cm_thread->in_progress(), "Can not start concurrent operation while in progress");
MutexLocker x(CGC_lock, Mutex::_no_safepoint_check_flag);
if (!_cm_thread->in_progress()) {
_cm_thread->set_started();
CGC_lock->notify();
if (concurrent_operation_is_full_mark) {
_cm->post_concurrent_mark_start();
_cm_thread->start_full_mark();
} else {
_cm->post_concurrent_undo_start();
_cm_thread->start_undo_mark();
}
CGC_lock->notify();
}
bool G1CollectedHeap::is_potential_eager_reclaim_candidate(HeapRegion* r) const {
@ -2975,13 +2981,11 @@ void G1CollectedHeap::do_collection_pause_at_safepoint_helper(double target_paus
// We also do not allow mixed GCs during marking.
assert(!collector_state()->mark_or_rebuild_in_progress() || collector_state()->in_young_only_phase(), "sanity");
// Record whether this pause is a concurrent start. When the current
// thread has completed its logging output and it's safe to signal
// the CM thread, the flag's value in the policy has been reset.
bool should_start_conc_mark = collector_state()->in_concurrent_start_gc();
if (should_start_conc_mark) {
_cm->gc_tracer_cm()->set_gc_cause(gc_cause());
}
// Record whether this pause may need to trigger a concurrent operation. Later,
// when we signal the G1ConcurrentMarkThread, the collector state has already
// been reset for the next pause.
bool should_start_concurrent_mark_operation = collector_state()->in_concurrent_start_gc();
bool concurrent_operation_is_full_mark = false;
// Inner scope for scope based logging, timers, and stats collection
{
@ -3058,25 +3062,19 @@ void G1CollectedHeap::do_collection_pause_at_safepoint_helper(double target_paus
_survivor_evac_stats.adjust_desired_plab_sz();
_old_evac_stats.adjust_desired_plab_sz();
if (should_start_conc_mark) {
// We have to do this before we notify the CM threads that
// they can start working to make sure that all the
// appropriate initialization is done on the CM object.
concurrent_mark()->post_concurrent_start();
// Note that we don't actually trigger the CM thread at
// this point. We do that later when we're sure that
// the current thread has completed its logging output.
}
allocate_dummy_regions();
_allocator->init_mutator_alloc_regions();
expand_heap_after_young_collection();
// Refine the type of a concurrent mark operation now that we did the
// evacuation, eventually aborting it.
concurrent_operation_is_full_mark = policy()->concurrent_operation_is_full_mark("Revise IHOP");
double sample_end_time_sec = os::elapsedTime();
double pause_time_ms = (sample_end_time_sec - sample_start_time_sec) * MILLIUNITS;
policy()->record_collection_pause_end(pause_time_ms);
policy()->record_collection_pause_end(pause_time_ms, concurrent_operation_is_full_mark);
}
verify_after_young_collection(verify_type);
@ -3117,13 +3115,13 @@ void G1CollectedHeap::do_collection_pause_at_safepoint_helper(double target_paus
// without its logging output interfering with the logging output
// that came from the pause.
if (should_start_conc_mark) {
// CAUTION: after the doConcurrentMark() call below, the concurrent marking
if (should_start_concurrent_mark_operation) {
// CAUTION: after the start_concurrent_cycle() call below, the concurrent marking
// thread(s) could be running concurrently with us. Make sure that anything
// after this point does not assume that we are the only GC thread running.
// Note: of course, the actual marking work will not start until the safepoint
// itself is released in SuspendibleThreadSet::desynchronize().
do_concurrent_mark();
start_concurrent_cycle(concurrent_operation_is_full_mark);
ConcurrentGCBreakpoints::notify_idle_to_active();
}
}
@ -3733,7 +3731,7 @@ void G1CollectedHeap::pre_evacuate_collection_set(G1EvacuationInfo& evacuation_i
// Concurrent start needs claim bits to keep track of the marked-through CLDs.
if (collector_state()->in_concurrent_start_gc()) {
concurrent_mark()->pre_concurrent_start();
concurrent_mark()->pre_concurrent_start(gc_cause());
double start_clear_claimed_marks = os::elapsedTime();

@ -746,6 +746,9 @@ private:
void reset_taskqueue_stats();
#endif // TASKQUEUE_STATS
// Start a concurrent cycle.
void start_concurrent_cycle(bool concurrent_operation_is_full_mark);
// Schedule the VM operation that will do an evacuation pause to
// satisfy an allocation request of word_size. *succeeded will
// return whether the VM operation was successful (it did do an
@ -1307,16 +1310,6 @@ public:
bool check_young_list_empty();
#endif
// *** Stuff related to concurrent marking. It's not clear to me that so
// many of these need to be public.
// The functions below are helper functions that a subclass of
// "CollectedHeap" can use in the implementation of its virtual
// functions.
// This performs a concurrent marking of the live objects in a
// bitmap off to the side.
void do_concurrent_mark();
bool is_marked_next(oop obj) const;
// Determine if an object is dead, given the object and also

@ -607,7 +607,7 @@ private:
// as asserts here to minimize their overhead on the product. However, we
// will have them as guarantees at the beginning / end of the bitmap
// clearing to get some checking in the product.
assert(_cm == NULL || _cm->cm_thread()->during_cycle(), "invariant");
assert(_cm == NULL || _cm->cm_thread()->in_progress(), "invariant");
assert(_cm == NULL || !G1CollectedHeap::heap()->collector_state()->mark_or_rebuild_in_progress(), "invariant");
}
assert(cur == end, "Must have completed iteration over the bitmap for region %u.", r->hrm_index());
@ -656,7 +656,7 @@ void G1ConcurrentMark::clear_bitmap(G1CMBitMap* bitmap, WorkGang* workers, bool
void G1ConcurrentMark::cleanup_for_next_mark() {
// Make sure that the concurrent mark thread looks to still be in
// the current cycle.
guarantee(cm_thread()->during_cycle(), "invariant");
guarantee(cm_thread()->in_progress(), "invariant");
// We are finishing up the current cycle by clearing the next
// marking bitmap and getting it ready for the next cycle. During
@ -667,7 +667,7 @@ void G1ConcurrentMark::cleanup_for_next_mark() {
clear_bitmap(_next_mark_bitmap, _concurrent_workers, true);
// Repeat the asserts from above.
guarantee(cm_thread()->during_cycle(), "invariant");
guarantee(cm_thread()->in_progress(), "invariant");
guarantee(!_g1h->collector_state()->mark_or_rebuild_in_progress(), "invariant");
}
@ -684,7 +684,7 @@ public:
}
};
void G1ConcurrentMark::pre_concurrent_start() {
void G1ConcurrentMark::pre_concurrent_start(GCCause::Cause cause) {
assert_at_safepoint_on_vm_thread();
// Reset marking state.
@ -695,10 +695,12 @@ void G1ConcurrentMark::pre_concurrent_start() {
_g1h->heap_region_iterate(&startcl);
_root_regions.reset();
_gc_tracer_cm->set_gc_cause(cause);
}
void G1ConcurrentMark::post_concurrent_start() {
void G1ConcurrentMark::post_concurrent_mark_start() {
// Start Concurrent Marking weak-reference discovery.
ReferenceProcessor* rp = _g1h->ref_processor_cm();
// enable ("weak") refs discovery
@ -719,6 +721,10 @@ void G1ConcurrentMark::post_concurrent_start() {
// during it. No need to call it here.
}
void G1ConcurrentMark::post_concurrent_undo_start() {
root_regions()->cancel_scan();
}
/*
* Notice that in the next two methods, we actually leave the STS
* during the barrier sync and join it immediately afterwards. If we
@ -1956,7 +1962,7 @@ void G1ConcurrentMark::print_stats() {
}
void G1ConcurrentMark::concurrent_cycle_abort() {
if (!cm_thread()->during_cycle() || _has_aborted) {
if (!cm_thread()->in_progress() || _has_aborted) {
// We haven't started a concurrent cycle or we have already aborted it. No need to do anything.
return;
}

@ -30,12 +30,14 @@
#include "gc/g1/g1HeapVerifier.hpp"
#include "gc/g1/g1RegionMarkStatsCache.hpp"
#include "gc/g1/heapRegionSet.hpp"
#include "gc/shared/gcCause.hpp"
#include "gc/shared/taskTerminator.hpp"
#include "gc/shared/taskqueue.hpp"
#include "gc/shared/verifyOption.hpp"
#include "gc/shared/workgroup.hpp"
#include "memory/allocation.hpp"
#include "utilities/compilerWarnings.hpp"
#include "utilities/numberSeq.hpp"
class ConcurrentGCTimer;
class G1ConcurrentMarkThread;
@ -305,7 +307,7 @@ class G1ConcurrentMark : public CHeapObj<mtGC> {
MemRegion const _heap;
// Root region tracking and claiming
G1CMRootMemRegions _root_regions;
G1CMRootMemRegions _root_regions;
// For grey objects
G1CMMarkStack _global_mark_stack; // Grey objects behind global finger
@ -542,8 +544,9 @@ public:
// These two methods do the work that needs to be done at the start and end of the
// concurrent start pause.
void pre_concurrent_start();
void post_concurrent_start();
void pre_concurrent_start(GCCause::Cause cause);
void post_concurrent_mark_start();
void post_concurrent_undo_start();
// Scan all the root regions and mark everything reachable from
// them.
@ -594,7 +597,6 @@ public:
inline bool is_marked_in_next_bitmap(oop p) const;
ConcurrentGCTimer* gc_timer_cm() const { return _gc_timer_cm; }
G1OldTracer* gc_tracer_cm() const { return _gc_tracer_cm; }
private:
// Rebuilds the remembered sets for chosen regions in parallel and concurrently to the application.

@ -42,6 +42,7 @@
#include "runtime/handles.inline.hpp"
#include "runtime/vmThread.hpp"
#include "utilities/debug.hpp"
#include "utilities/formatBuffer.hpp"
#include "utilities/ticks.hpp"
G1ConcurrentMarkThread::G1ConcurrentMarkThread(G1ConcurrentMark* cm) :
@ -135,13 +136,22 @@ void G1ConcurrentMarkThread::run_service() {
_vtime_start = os::elapsedVTime();
while (wait_for_next_cycle()) {
assert(in_progress(), "must be");
GCIdMark gc_id_mark;
GCTraceConcTime(Info, gc) tt("Concurrent Cycle");
GCTraceConcTime(Info, gc) tt(FormatBuffer<128>("Concurrent %s Cycle",
_state == FullMark ? "Mark" : "Undo"));
concurrent_cycle_start();
full_concurrent_cycle_do();
concurrent_cycle_end();
if (_state == FullMark) {
concurrent_mark_cycle_do();
} else {
assert(_state == UndoMark, "Must do undo mark but is %d", _state);
concurrent_undo_cycle_do();
}
concurrent_cycle_end(_state == FullMark && !_cm->has_aborted());
_vtime_accum = (os::elapsedVTime() - _vtime_start);
}
@ -157,14 +167,10 @@ bool G1ConcurrentMarkThread::wait_for_next_cycle() {
assert(!in_progress(), "should have been cleared");
MonitorLocker ml(CGC_lock, Mutex::_no_safepoint_check_flag);
while (!started() && !should_terminate()) {
while (!in_progress() && !should_terminate()) {
ml.wait();
}
if (started()) {
set_in_progress();
}
return !should_terminate();
}
@ -269,7 +275,7 @@ void G1ConcurrentMarkThread::concurrent_cycle_start() {
_cm->concurrent_cycle_start();
}
void G1ConcurrentMarkThread::full_concurrent_cycle_do() {
void G1ConcurrentMarkThread::concurrent_mark_cycle_do() {
HandleMark hm(Thread::current());
ResourceMark rm;
@ -289,6 +295,9 @@ void G1ConcurrentMarkThread::full_concurrent_cycle_do() {
//
// For the same reason ConcurrentGCBreakpoints (in the phase methods) before
// here risk deadlock, because a young GC must wait for root region scanning.
//
// We can not easily abort before root region scan either because of the
// reasons mentioned in G1CollectedHeap::abort_concurrent_cycle().
// Phase 2: Scan root regions.
if (phase_scan_root_regions()) return;
@ -309,14 +318,26 @@ void G1ConcurrentMarkThread::full_concurrent_cycle_do() {
phase_clear_bitmap_for_next_mark();
}
void G1ConcurrentMarkThread::concurrent_cycle_end() {
void G1ConcurrentMarkThread::concurrent_undo_cycle_do() {
HandleMark hm(Thread::current());
ResourceMark rm;
// We can (and should) abort if there has been a concurrent cycle abort for
// some reason.
if (_cm->has_aborted()) { return; }
// Phase 1: Clear bitmap for next mark.
phase_clear_bitmap_for_next_mark();
}
void G1ConcurrentMarkThread::concurrent_cycle_end(bool mark_cycle_completed) {
// Update the number of full collections that have been
// completed. This will also notify the G1OldGCCount_lock in case a
// Java thread is waiting for a full GC to happen (e.g., it
// called System.gc() with +ExplicitGCInvokesConcurrent).
SuspendibleThreadSetJoiner sts_join;
G1CollectedHeap::heap()->increment_old_marking_cycles_completed(true /* concurrent */,
!_cm->has_aborted());
mark_cycle_completed /* heap_examined */);
_cm->concurrent_cycle_end();
ConcurrentGCBreakpoints::notify_active_to_idle();

@ -40,15 +40,15 @@ class G1ConcurrentMarkThread: public ConcurrentGCThread {
G1ConcurrentMark* _cm;
enum ServiceState {
enum ServiceState : uint {
Idle,
Started,
InProgress
FullMark,
UndoMark
};
volatile ServiceState _state;
// Wait for next cycle. Returns false if the service should be stopped.
// Wait for next cycle. Returns the command passed over.
bool wait_for_next_cycle();
bool mark_loop_needs_restart() const;
@ -74,8 +74,10 @@ class G1ConcurrentMarkThread: public ConcurrentGCThread {
void concurrent_cycle_start();
void full_concurrent_cycle_do();
void concurrent_cycle_end();
void concurrent_mark_cycle_do();
void concurrent_undo_cycle_do();
void concurrent_cycle_end(bool mark_cycle_completed);
// Delay pauses to meet MMU.
void delay_to_keep_mmu(bool remark);
@ -93,24 +95,20 @@ class G1ConcurrentMarkThread: public ConcurrentGCThread {
// Marking virtual time so far this thread and concurrent marking tasks.
double vtime_mark_accum();
G1ConcurrentMark* cm() { return _cm; }
G1ConcurrentMark* cm() { return _cm; }
void set_idle() { assert(_state != Started, "must not be starting a new cycle"); _state = Idle; }
bool idle() { return _state == Idle; }
void set_started() { assert(_state == Idle, "cycle in progress"); _state = Started; }
bool started() { return _state == Started; }
void set_in_progress() { assert(_state == Started, "must be starting a cycle"); _state = InProgress; }
bool in_progress() { return _state == InProgress; }
void set_idle();
void start_full_mark();
void start_undo_mark();
void set_in_progress();
// Returns true from the moment a marking cycle is
bool idle() const;
// Returns true from the moment a concurrent cycle is
// initiated (during the concurrent start pause when started() is set)
// to the moment when the cycle completes (just after the next
// marking bitmap has been cleared and in_progress() is
// cleared). While during_cycle() is true we will not start another cycle
// so that cycles do not overlap. We cannot use just in_progress()
// as the CM thread might take some time to wake up before noticing
// that started() is set and set in_progress().
bool during_cycle() { return !idle(); }
// cleared).
bool in_progress() const;
};
#endif // SHARE_GC_G1_G1CONCURRENTMARKTHREAD_HPP

@ -38,4 +38,25 @@ inline double G1ConcurrentMarkThread::vtime_mark_accum() {
return _cm->all_task_accum_vtime();
}
inline void G1ConcurrentMarkThread::set_idle() {
assert(_state == FullMark || _state == UndoMark, "must not be starting a new cycle");
_state = Idle;
}
inline void G1ConcurrentMarkThread::start_full_mark() {
assert(_state == Idle, "cycle in progress");
_state = FullMark;
}
inline void G1ConcurrentMarkThread::start_undo_mark() {
assert(_state == Idle, "cycle in progress");
_state = UndoMark;
}
inline bool G1ConcurrentMarkThread::idle() const { return _state == Idle; }
inline bool G1ConcurrentMarkThread::in_progress() const {
return !idle();
}
#endif // SHARE_GC_G1_G1CONCURRENTMARKTHREAD_INLINE_HPP

@ -39,8 +39,8 @@ void G1HeterogeneousHeapPolicy::init(G1CollectedHeap* g1h, G1CollectionSet* coll
}
// After a collection pause, young list target length is updated. So we need to make sure we have enough regions in dram for young gen.
void G1HeterogeneousHeapPolicy::record_collection_pause_end(double pause_time_ms) {
G1Policy::record_collection_pause_end(pause_time_ms);
void G1HeterogeneousHeapPolicy::record_collection_pause_end(double pause_time_ms, bool concurrent_operation_is_full_mark) {
G1Policy::record_collection_pause_end(pause_time_ms, concurrent_operation_is_full_mark);
_manager->adjust_dram_regions((uint)young_list_target_length(), G1CollectedHeap::heap()->workers());
}

@ -38,7 +38,7 @@ public:
// initialize policy
virtual void init(G1CollectedHeap* g1h, G1CollectionSet* collection_set);
// Record end of an evacuation pause.
virtual void record_collection_pause_end(double pause_time_ms);
virtual void record_collection_pause_end(double pause_time_ms, bool concurrent_operation_is_full_mark);
// Record the end of full collection.
virtual void record_full_collection_end();

@ -452,7 +452,7 @@ void G1Policy::record_full_collection_end() {
// transitions and make sure we start with young GCs after the Full GC.
collector_state()->set_in_young_only_phase(true);
collector_state()->set_in_young_gc_before_mixed(false);
collector_state()->set_initiate_conc_mark_if_possible(need_to_start_conc_mark("end of Full GC", 0));
collector_state()->set_initiate_conc_mark_if_possible(need_to_start_conc_mark("end of Full GC"));
collector_state()->set_in_concurrent_start_gc(false);
collector_state()->set_mark_or_rebuild_in_progress(false);
collector_state()->set_clearing_next_bitmap(false);
@ -547,7 +547,7 @@ void G1Policy::record_collection_pause_start(double start_time_sec) {
assert(_g1h->collection_set()->verify_young_ages(), "region age verification failed");
}
void G1Policy::record_concurrent_mark_init_end(double mark_init_elapsed_time_ms) {
void G1Policy::record_concurrent_mark_init_end() {
assert(!collector_state()->initiate_conc_mark_if_possible(), "we should have cleared it by now");
collector_state()->set_in_concurrent_start_gc(false);
}
@ -591,7 +591,7 @@ double G1Policy::constant_other_time_ms(double pause_time_ms) const {
}
bool G1Policy::about_to_start_mixed_phase() const {
return _g1h->concurrent_mark()->cm_thread()->during_cycle() || collector_state()->in_young_gc_before_mixed();
return _g1h->concurrent_mark()->cm_thread()->in_progress() || collector_state()->in_young_gc_before_mixed();
}
bool G1Policy::need_to_start_conc_mark(const char* source, size_t alloc_word_size) {
@ -612,10 +612,14 @@ bool G1Policy::need_to_start_conc_mark(const char* source, size_t alloc_word_siz
result ? "Request concurrent cycle initiation (occupancy higher than threshold)" : "Do not request concurrent cycle initiation (still doing mixed collections)",
cur_used_bytes, alloc_byte_size, marking_initiating_used_threshold, (double) marking_initiating_used_threshold / _g1h->capacity() * 100, source);
}
return result;
}
bool G1Policy::concurrent_operation_is_full_mark(const char* msg) {
return collector_state()->in_concurrent_start_gc() &&
((_g1h->gc_cause() != GCCause::_g1_humongous_allocation) || need_to_start_conc_mark(msg));
}
double G1Policy::logged_cards_processing_time() const {
double all_cards_processing_time = average_time_ms(G1GCPhaseTimes::ScanHR) + average_time_ms(G1GCPhaseTimes::OptScanHR);
size_t logged_dirty_cards = phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergeLB, G1GCPhaseTimes::MergeLBDirtyCards);
@ -631,18 +635,18 @@ double G1Policy::logged_cards_processing_time() const {
// Anything below that is considered to be zero
#define MIN_TIMER_GRANULARITY 0.0000001
void G1Policy::record_collection_pause_end(double pause_time_ms) {
void G1Policy::record_collection_pause_end(double pause_time_ms, bool concurrent_operation_is_full_mark) {
G1GCPhaseTimes* p = phase_times();
double end_time_sec = os::elapsedTime();
double start_time_sec = phase_times()->cur_collection_start_sec();
PauseKind this_pause = young_gc_pause_kind();
PauseKind this_pause = young_gc_pause_kind(concurrent_operation_is_full_mark);
bool update_stats = should_update_gc_stats();
if (is_concurrent_start_pause(this_pause)) {
record_concurrent_mark_init_end(0.0);
record_concurrent_mark_init_end();
} else {
maybe_start_marking();
}
@ -781,7 +785,7 @@ void G1Policy::record_collection_pause_end(double pause_time_ms) {
assert(!(is_concurrent_start_pause(this_pause) && collector_state()->mark_or_rebuild_in_progress()),
"If the last pause has been concurrent start, we should not have been in the marking window");
if (is_concurrent_start_pause(this_pause)) {
collector_state()->set_mark_or_rebuild_in_progress(true);
collector_state()->set_mark_or_rebuild_in_progress(concurrent_operation_is_full_mark);
}
_free_regions_at_end_of_collection = _g1h->num_free_regions();
@ -810,7 +814,7 @@ void G1Policy::record_collection_pause_end(double pause_time_ms) {
// for completing the marking, i.e. are faster than expected.
// This skews the predicted marking length towards smaller values which might cause
// the mark start being too late.
_concurrent_start_to_mixed.reset();
abort_time_to_mixed_tracking();
}
// Note that _mmu_tracker->max_gc_time() returns the time in seconds.
@ -1025,7 +1029,7 @@ bool G1Policy::force_concurrent_start_if_outside_cycle(GCCause::Cause gc_cause)
// We actually check whether we are marking here and not if we are in a
// reclamation phase. This means that we will schedule a concurrent mark
// even while we are still in the process of reclaiming memory.
bool during_cycle = _g1h->concurrent_mark()->cm_thread()->during_cycle();
bool during_cycle = _g1h->concurrent_mark()->cm_thread()->in_progress();
if (!during_cycle) {
log_debug(gc, ergo)("Request concurrent cycle initiation (requested by GC cause). "
"GC cause: %s",
@ -1156,7 +1160,10 @@ bool G1Policy::is_young_only_pause(PauseKind kind) {
assert(kind != FullGC, "must be");
assert(kind != Remark, "must be");
assert(kind != Cleanup, "must be");
return kind == ConcurrentStartGC || kind == LastYoungGC || kind == YoungOnlyGC;
return kind == ConcurrentStartUndoGC ||
kind == ConcurrentStartMarkGC ||
kind == LastYoungGC ||
kind == YoungOnlyGC;
}
bool G1Policy::is_mixed_pause(PauseKind kind) {
@ -1171,14 +1178,14 @@ bool G1Policy::is_last_young_pause(PauseKind kind) {
}
bool G1Policy::is_concurrent_start_pause(PauseKind kind) {
return kind == ConcurrentStartGC;
return kind == ConcurrentStartMarkGC || kind == ConcurrentStartUndoGC;
}
G1Policy::PauseKind G1Policy::young_gc_pause_kind() const {
G1Policy::PauseKind G1Policy::young_gc_pause_kind(bool concurrent_operation_is_full_mark) const {
assert(!collector_state()->in_full_gc(), "must be");
if (collector_state()->in_concurrent_start_gc()) {
assert(!collector_state()->in_young_gc_before_mixed(), "must be");
return ConcurrentStartGC;
return concurrent_operation_is_full_mark ? ConcurrentStartMarkGC : ConcurrentStartUndoGC;
} else if (collector_state()->in_young_gc_before_mixed()) {
assert(!collector_state()->in_concurrent_start_gc(), "must be");
return LastYoungGC;
@ -1214,7 +1221,9 @@ void G1Policy::update_gc_pause_time_ratios(PauseKind kind, double start_time_sec
}
}
void G1Policy::record_pause(PauseKind kind, double start, double end) {
void G1Policy::record_pause(PauseKind kind,
double start,
double end) {
// Manage the MMU tracker. For some reason it ignores Full GCs.
if (kind != FullGC) {
_mmu_tracker->add_pause(start, end);
@ -1224,6 +1233,12 @@ void G1Policy::record_pause(PauseKind kind, double start, double end) {
update_gc_pause_time_ratios(kind, start, end);
}
update_time_to_mixed_tracking(kind, start, end);
}
void G1Policy::update_time_to_mixed_tracking(PauseKind kind,
double start,
double end) {
// Manage the mutator time tracking from concurrent start to first mixed gc.
switch (kind) {
case FullGC:
@ -1235,11 +1250,19 @@ void G1Policy::record_pause(PauseKind kind, double start, double end) {
case LastYoungGC:
_concurrent_start_to_mixed.add_pause(end - start);
break;
case ConcurrentStartGC:
case ConcurrentStartMarkGC:
// Do not track time-to-mixed time for periodic collections as they are likely
// to be not representative to regular operation as the mutators are idle at
// that time. Also only track full concurrent mark cycles.
if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
_concurrent_start_to_mixed.record_concurrent_start_end(end);
}
break;
case ConcurrentStartUndoGC:
assert(_g1h->gc_cause() == GCCause::_g1_humongous_allocation,
"GC cause must be humongous allocation but is %d",
_g1h->gc_cause());
break;
case MixedGC:
_concurrent_start_to_mixed.record_mixed_gc_start(start);
break;

@ -264,12 +264,13 @@ private:
void maybe_start_marking();
// The kind of STW pause.
enum PauseKind {
enum PauseKind : uint {
FullGC,
YoungOnlyGC,
MixedGC,
LastYoungGC,
ConcurrentStartGC,
ConcurrentStartMarkGC,
ConcurrentStartUndoGC,
Cleanup,
Remark
};
@ -279,7 +280,9 @@ private:
static bool is_last_young_pause(PauseKind kind);
static bool is_concurrent_start_pause(PauseKind kind);
// Calculate PauseKind from internal state.
PauseKind young_gc_pause_kind() const;
PauseKind young_gc_pause_kind(bool concurrent_operation_is_full_mark) const;
// Manage time-to-mixed tracking.
void update_time_to_mixed_tracking(PauseKind pause, double start, double end);
// Record the given STW pause with the given start and end times (in s).
void record_pause(PauseKind kind, double start, double end);
@ -319,18 +322,20 @@ public:
bool need_to_start_conc_mark(const char* source, size_t alloc_word_size = 0);
bool concurrent_operation_is_full_mark(const char* msg = NULL);
bool about_to_start_mixed_phase() const;
// Record the start and end of an evacuation pause.
void record_collection_pause_start(double start_time_sec);
virtual void record_collection_pause_end(double pause_time_ms);
virtual void record_collection_pause_end(double pause_time_ms, bool concurrent_operation_is_full_mark);
// Record the start and end of a full collection.
void record_full_collection_start();
virtual void record_full_collection_end();
// Must currently be called while the world is stopped.
void record_concurrent_mark_init_end(double mark_init_elapsed_time_ms);
void record_concurrent_mark_init_end();
// Record start and end of remark.
void record_concurrent_mark_remark_start();

@ -59,7 +59,7 @@ void G1ServiceThread::sleep_before_next_cycle() {
bool G1ServiceThread::should_start_periodic_gc() {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
// If we are currently in a concurrent mark we are going to uncommit memory soon.
if (g1h->concurrent_mark()->cm_thread()->during_cycle()) {
if (g1h->concurrent_mark()->cm_thread()->in_progress()) {
log_debug(gc, periodic)("Concurrent cycle in progress. Skipping.");
return false;
}

@ -92,7 +92,7 @@
#include "gc/g1/g1Arguments.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1ConcurrentMark.hpp"
#include "gc/g1/g1ConcurrentMarkThread.hpp"
#include "gc/g1/g1ConcurrentMarkThread.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heterogeneousHeapRegionManager.hpp"
#endif // INCLUDE_G1GC
@ -473,7 +473,7 @@ WB_END
WB_ENTRY(jboolean, WB_G1InConcurrentMark(JNIEnv* env, jobject o))
if (UseG1GC) {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
return g1h->concurrent_mark()->cm_thread()->during_cycle();
return g1h->concurrent_mark()->cm_thread()->in_progress();
}
THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_G1InConcurrentMark: G1 GC is not enabled");
WB_END
@ -481,7 +481,7 @@ WB_END
WB_ENTRY(jboolean, WB_G1StartMarkCycle(JNIEnv* env, jobject o))
if (UseG1GC) {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
if (!g1h->concurrent_mark()->cm_thread()->during_cycle()) {
if (!g1h->concurrent_mark()->cm_thread()->in_progress()) {
g1h->collect(GCCause::_wb_conc_mark);
return true;
}

@ -0,0 +1,129 @@
/*
* Copyright (c) 2020, 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.
*/
package gc.g1;
/*
* @test TestHumongousConcurrentStartUndo
* @summary Tests an alternating sequence of Concurrent Mark and Concurrent Undo
* cycles.
* reclaim heap occupancy falls below the IHOP value.
* @requires vm.gc.G1
* @library /test/lib /testlibrary /
* @modules java.base/jdk.internal.misc
* java.management
* @build sun.hotspot.WhiteBox
* @run driver ClassFileInstaller sun.hotspot.WhiteBox
* sun.hotspot.WhiteBox$WhiteBoxPermission
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* gc.g1.TestHumongousConcurrentStartUndo
*/
import gc.testlibrary.Helpers;
import sun.hotspot.WhiteBox;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class TestHumongousConcurrentStartUndo {
// Heap sizes < 224 MB are increased to 224 MB if vm_page_size == 64K to
// fulfill alignment constraints.
private static final int HeapSize = 224; // MB
private static final int HeapRegionSize = 1; // MB
private static final int InitiatingHeapOccupancyPercent = 50; // %
private static final int YoungSize = HeapSize / 8;
public static void main(String[] args) throws Exception {
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
"-Xbootclasspath/a:.",
"-XX:+UseG1GC",
"-Xms" + HeapSize + "m",
"-Xmx" + HeapSize + "m",
"-Xmn" + YoungSize + "m",
"-XX:G1HeapRegionSize=" + HeapRegionSize + "m",
"-XX:InitiatingHeapOccupancyPercent=" + InitiatingHeapOccupancyPercent,
"-XX:-G1UseAdaptiveIHOP",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+WhiteBoxAPI",
"-Xlog:gc*",
EdenObjectAllocatorWithHumongousAllocation.class.getName());
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldContain("Pause Young (Concurrent Start) (G1 Humongous Allocation)");
output.shouldContain("Concurrent Undo Cycle");
output.shouldContain("Concurrent Mark Cycle");
output.shouldHaveExitValue(0);
System.out.println(output.getStdout());
}
static class EdenObjectAllocatorWithHumongousAllocation {
private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
private static void allocateHumongous(int num, int objSize, Queue keeper) {
for (int i = 1; i <= num; i++) {
if (i % 10 == 0) {
System.out.println("Allocating humongous object " + i + "/" + num +
" of size " + objSize + " bytes");
}
byte[] e = new byte[objSize];
if (!keeper.offer(e)) {
keeper.remove();
keeper.offer(e);
}
}
}
public static void main(String [] args) throws Exception {
final int M = 1024 * 1024;
// Make humongous object size 75% of region size
final int humongousObjectSize =
(int)(HeapRegionSize * M * 0.75);
// Number of objects to allocate to go above IHOP
final int humongousObjectAllocations =
(int)(((HeapSize - YoungSize) * 80 / 100.0) / HeapRegionSize);
ArrayBlockingQueue a;
for (int iterate = 0; iterate < 3; iterate++) {
WHITE_BOX.fullGC();
a = new ArrayBlockingQueue(1);
allocateHumongous(humongousObjectAllocations, humongousObjectSize, a);
Helpers.waitTillCMCFinished(WHITE_BOX, 1);
a = null;
a = new ArrayBlockingQueue(humongousObjectAllocations);
allocateHumongous(humongousObjectAllocations, humongousObjectSize, a);
Helpers.waitTillCMCFinished(WHITE_BOX, 1);
a = null;
allocateHumongous(1, humongousObjectSize, new ArrayBlockingQueue(1));
Helpers.waitTillCMCFinished(WHITE_BOX, 1);
}
}
}
}

@ -1,46 +0,0 @@
/*
* Copyright (c) 2015, 2020, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.jfr.event.gc.detailed;
/**
* @test
* @key randomness
* @summary Test allocates humongous objects with G1 GC. Objects
* considered humongous when it allocates equals or more than one region. As
* we're passing the size of byte array we need adjust it that entire structure
* fits exactly to one region, if not - G1 will allocate another almost empty
* region as a continue of humongous. Thus we will exhaust memory very fast and
* test will fail with OOME.
* @requires vm.hasJFR
* @requires vm.gc == "null" | vm.gc == "G1"
* @library /test/lib /test/jdk
* @run main/othervm -XX:+UseG1GC -XX:MaxNewSize=5m -Xmx256m -XX:G1HeapRegionSize=1048576 jdk.jfr.event.gc.detailed.TestStressBigAllocationGCEventsWithG1 1048544
*/
public class TestStressBigAllocationGCEventsWithG1 {
public static void main(String[] args) throws Exception {
new StressAllocationGCEvents().run(args);
}
}