# HG changeset patch # User tonyp # Date 1315412483 14400 # Node ID 20213c8a3c40f4575aedf62918075c968b3d8a5f # Parent 27702f0120170de338bc32a4765491fed19dd89f 7050392: G1: Introduce flag to generate a log of the G1 ergonomic decisions Summary: It introduces ergonomic decision logging in G1 for the following heuristics: heap sizing, collection set construction, concurrent cycle initiation, and partially-young GC start/end. The code has a bit of refactoring in a few places to make the decision logging possible. It also replaces alternative ad-hoc logging that we have under different parameters and switches (G1_DEBUG, G1PolicyVerbose). Reviewed-by: johnc, ysr diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/collectionSetChooser.cpp --- a/src/share/vm/gc_implementation/g1/collectionSetChooser.cpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/collectionSetChooser.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2011, 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 @@ -26,6 +26,7 @@ #include "gc_implementation/g1/collectionSetChooser.hpp" #include "gc_implementation/g1/g1CollectedHeap.inline.hpp" #include "gc_implementation/g1/g1CollectorPolicy.hpp" +#include "gc_implementation/g1/g1ErgoVerbose.hpp" #include "memory/space.inline.hpp" CSetChooserCache::CSetChooserCache() { @@ -358,6 +359,9 @@ if (_cache.is_empty()) { assert(_curMarkedIndex == _numMarkedRegions, "if cache is empty, list should also be empty"); + ergo_verbose0(ErgoCSetConstruction, + "stop adding old regions to CSet", + ergo_format_reason("cache is empty")); return NULL; } @@ -368,10 +372,23 @@ if (g1p->adaptive_young_list_length()) { if (time_remaining - predicted_time < 0.0) { g1h->check_if_region_is_too_expensive(predicted_time); + ergo_verbose2(ErgoCSetConstruction, + "stop adding old regions to CSet", + ergo_format_reason("predicted old region time higher than remaining time") + ergo_format_ms("predicted old region time") + ergo_format_ms("remaining time"), + predicted_time, time_remaining); return NULL; } } else { - if (predicted_time > 2.0 * avg_prediction) { + double threshold = 2.0 * avg_prediction; + if (predicted_time > threshold) { + ergo_verbose2(ErgoCSetConstruction, + "stop adding old regions to CSet", + ergo_format_reason("predicted old region time higher than threshold") + ergo_format_ms("predicted old region time") + ergo_format_ms("threshold"), + predicted_time, threshold); return NULL; } } diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/concurrentMark.cpp --- a/src/share/vm/gc_implementation/g1/concurrentMark.cpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/concurrentMark.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -28,6 +28,7 @@ #include "gc_implementation/g1/concurrentMarkThread.inline.hpp" #include "gc_implementation/g1/g1CollectedHeap.inline.hpp" #include "gc_implementation/g1/g1CollectorPolicy.hpp" +#include "gc_implementation/g1/g1ErgoVerbose.hpp" #include "gc_implementation/g1/g1OopClosures.inline.hpp" #include "gc_implementation/g1/g1RemSet.hpp" #include "gc_implementation/g1/heapRegionRemSet.hpp" @@ -1727,18 +1728,21 @@ size_t known_garbage_bytes = g1_par_count_task.used_bytes() - g1_par_count_task.live_bytes(); -#if 0 - gclog_or_tty->print_cr("used %1.2lf, live %1.2lf, garbage %1.2lf", - (double) g1_par_count_task.used_bytes() / (double) (1024 * 1024), - (double) g1_par_count_task.live_bytes() / (double) (1024 * 1024), - (double) known_garbage_bytes / (double) (1024 * 1024)); -#endif // 0 g1p->set_known_garbage_bytes(known_garbage_bytes); size_t start_used_bytes = g1h->used(); _at_least_one_mark_complete = true; g1h->set_marking_complete(); + ergo_verbose4(ErgoConcCycles, + "finish cleanup", + ergo_format_byte("occupancy") + ergo_format_byte("capacity") + ergo_format_byte_perc("known garbage"), + start_used_bytes, g1h->capacity(), + known_garbage_bytes, + ((double) known_garbage_bytes / (double) g1h->capacity()) * 100.0); + double count_end = os::elapsedTime(); double this_final_counting_time = (count_end - start); if (G1PrintParCleanupStats) { diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp --- a/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -31,6 +31,7 @@ #include "gc_implementation/g1/g1AllocRegion.inline.hpp" #include "gc_implementation/g1/g1CollectedHeap.inline.hpp" #include "gc_implementation/g1/g1CollectorPolicy.hpp" +#include "gc_implementation/g1/g1ErgoVerbose.hpp" #include "gc_implementation/g1/g1MarkSweep.hpp" #include "gc_implementation/g1/g1OopClosures.inline.hpp" #include "gc_implementation/g1/g1RemSet.inline.hpp" @@ -577,6 +578,11 @@ res = new_region_try_secondary_free_list(); } if (res == NULL && do_expand) { + ergo_verbose1(ErgoHeapSizing, + "attempt heap expansion", + ergo_format_reason("region allocation request failed") + ergo_format_byte("allocation request"), + word_size * HeapWordSize); if (expand(word_size * HeapWordSize)) { // Even though the heap was expanded, it might not have reached // the desired size. So, we cannot assume that the allocation @@ -790,6 +796,11 @@ // room available. assert(num_regions > fs, "earlier allocation should have succeeded"); + ergo_verbose1(ErgoHeapSizing, + "attempt heap expansion", + ergo_format_reason("humongous allocation request failed") + ergo_format_byte("allocation request"), + word_size * HeapWordSize); if (expand((num_regions - fs) * HeapRegion::GrainBytes)) { // Even though the heap was expanded, it might not have // reached the desired size. So, we cannot assume that the @@ -906,6 +917,8 @@ if (GC_locker::is_active_and_needs_gc()) { if (g1_policy()->can_expand_young_list()) { + // No need for an ergo verbose message here, + // can_expand_young_list() does this when it returns true. result = _mutator_alloc_region.attempt_allocation_force(word_size, false /* bot_updates */); if (result != NULL) { @@ -1477,63 +1490,34 @@ // we'll try to make the capacity smaller than it, not greater). maximum_desired_capacity = MAX2(maximum_desired_capacity, min_heap_size); - if (PrintGC && Verbose) { - const double free_percentage = - (double) free_after_gc / (double) capacity_after_gc; - gclog_or_tty->print_cr("Computing new size after full GC "); - gclog_or_tty->print_cr(" " - " minimum_free_percentage: %6.2f", - minimum_free_percentage); - gclog_or_tty->print_cr(" " - " maximum_free_percentage: %6.2f", - maximum_free_percentage); - gclog_or_tty->print_cr(" " - " capacity: %6.1fK" - " minimum_desired_capacity: %6.1fK" - " maximum_desired_capacity: %6.1fK", - (double) capacity_after_gc / (double) K, - (double) minimum_desired_capacity / (double) K, - (double) maximum_desired_capacity / (double) K); - gclog_or_tty->print_cr(" " - " free_after_gc: %6.1fK" - " used_after_gc: %6.1fK", - (double) free_after_gc / (double) K, - (double) used_after_gc / (double) K); - gclog_or_tty->print_cr(" " - " free_percentage: %6.2f", - free_percentage); - } if (capacity_after_gc < minimum_desired_capacity) { // Don't expand unless it's significant size_t expand_bytes = minimum_desired_capacity - capacity_after_gc; - if (expand(expand_bytes)) { - if (PrintGC && Verbose) { - gclog_or_tty->print_cr(" " - " expanding:" - " max_heap_size: %6.1fK" - " minimum_desired_capacity: %6.1fK" - " expand_bytes: %6.1fK", - (double) max_heap_size / (double) K, - (double) minimum_desired_capacity / (double) K, - (double) expand_bytes / (double) K); - } - } + ergo_verbose4(ErgoHeapSizing, + "attempt heap expansion", + ergo_format_reason("capacity lower than " + "min desired capacity after Full GC") + ergo_format_byte("capacity") + ergo_format_byte("occupancy") + ergo_format_byte_perc("min desired capacity"), + capacity_after_gc, used_after_gc, + minimum_desired_capacity, (double) MinHeapFreeRatio); + expand(expand_bytes); // No expansion, now see if we want to shrink } else if (capacity_after_gc > maximum_desired_capacity) { // Capacity too large, compute shrinking size size_t shrink_bytes = capacity_after_gc - maximum_desired_capacity; + ergo_verbose4(ErgoHeapSizing, + "attempt heap shrinking", + ergo_format_reason("capacity higher than " + "max desired capacity after Full GC") + ergo_format_byte("capacity") + ergo_format_byte("occupancy") + ergo_format_byte_perc("max desired capacity"), + capacity_after_gc, used_after_gc, + maximum_desired_capacity, (double) MaxHeapFreeRatio); shrink(shrink_bytes); - if (PrintGC && Verbose) { - gclog_or_tty->print_cr(" " - " shrinking:" - " min_heap_size: %6.1fK" - " maximum_desired_capacity: %6.1fK" - " shrink_bytes: %6.1fK", - (double) min_heap_size / (double) K, - (double) maximum_desired_capacity / (double) K, - (double) shrink_bytes / (double) K); - } } } @@ -1619,6 +1603,11 @@ verify_region_sets_optional(); size_t expand_bytes = MAX2(word_size * HeapWordSize, MinHeapDeltaBytes); + ergo_verbose1(ErgoHeapSizing, + "attempt heap expansion", + ergo_format_reason("allocation request failed") + ergo_format_byte("allocation request"), + word_size * HeapWordSize); if (expand(expand_bytes)) { _hrs.verify_optional(); verify_region_sets_optional(); @@ -1646,11 +1635,11 @@ size_t aligned_expand_bytes = ReservedSpace::page_align_size_up(expand_bytes); aligned_expand_bytes = align_size_up(aligned_expand_bytes, HeapRegion::GrainBytes); - - if (Verbose && PrintGC) { - gclog_or_tty->print("Expanding garbage-first heap from %ldK by %ldK", - old_mem_size/K, aligned_expand_bytes/K); - } + ergo_verbose2(ErgoHeapSizing, + "expand the heap", + ergo_format_byte("requested expansion amount") + ergo_format_byte("attempted expansion amount"), + expand_bytes, aligned_expand_bytes); // First commit the memory. HeapWord* old_end = (HeapWord*) _g1_storage.high(); @@ -1694,6 +1683,9 @@ assert(curr == mr.end(), "post-condition"); } } else { + ergo_verbose0(ErgoHeapSizing, + "did not expand the heap", + ergo_format_reason("heap expansion operation failed")); // The expansion of the virtual storage space was unsuccessful. // Let's see if it was because we ran out of swap. if (G1ExitOnExpansionFailure && @@ -1702,13 +1694,6 @@ vm_exit_out_of_memory(aligned_expand_bytes, "G1 heap expansion"); } } - - if (Verbose && PrintGC) { - size_t new_mem_size = _g1_storage.committed_size(); - gclog_or_tty->print_cr("...%s, expanded to %ldK", - (successful ? "Successful" : "Failed"), - new_mem_size/K); - } return successful; } @@ -1722,6 +1707,13 @@ MemRegion mr = _hrs.shrink_by(aligned_shrink_bytes, &num_regions_deleted); HeapWord* old_end = (HeapWord*) _g1_storage.high(); assert(mr.end() == old_end, "post-condition"); + + ergo_verbose3(ErgoHeapSizing, + "shrink the heap", + ergo_format_byte("requested shrinking amount") + ergo_format_byte("aligned shrinking amount") + ergo_format_byte("attempted shrinking amount"), + shrink_bytes, aligned_shrink_bytes, mr.byte_size()); if (mr.byte_size() > 0) { if (_hr_printer.is_active()) { HeapWord* curr = mr.end(); @@ -1740,13 +1732,10 @@ _expansion_regions += num_regions_deleted; update_committed_space(old_end, new_end); HeapRegionRemSet::shrink_heap(n_regions()); - - if (Verbose && PrintGC) { - size_t new_mem_size = _g1_storage.committed_size(); - gclog_or_tty->print_cr("Shrinking garbage-first heap from %ldK by %ldK to %ldK", - old_mem_size/K, aligned_shrink_bytes/K, - new_mem_size/K); - } + } else { + ergo_verbose0(ErgoHeapSizing, + "did not shrink the heap", + ergo_format_reason("heap shrinking operation failed")); } } @@ -3579,6 +3568,8 @@ size_t expand_bytes = g1_policy()->expansion_amount(); if (expand_bytes > 0) { size_t bytes_before = capacity(); + // No need for an ergo verbose message here, + // expansion_amount() does this when it returns a value > 0. if (!expand(expand_bytes)) { // We failed to expand the heap so let's verify that // committed/uncommitted amount match the backing store @@ -3732,13 +3723,6 @@ bool do_object_b(oop p) { // It is reachable if it is outside the collection set, or is inside // and forwarded. - -#ifdef G1_DEBUG - gclog_or_tty->print_cr("is alive "PTR_FORMAT" in CS %d forwarded %d overall %d", - (void*) p, _g1->obj_in_cs(p), p->is_forwarded(), - !_g1->obj_in_cs(p) || p->is_forwarded()); -#endif // G1_DEBUG - return !_g1->obj_in_cs(p) || p->is_forwarded(); } }; @@ -3750,20 +3734,9 @@ void do_oop(narrowOop* p) { guarantee(false, "Not needed"); } void do_oop( oop* p) { oop obj = *p; -#ifdef G1_DEBUG - if (PrintGC && Verbose) { - gclog_or_tty->print_cr("keep alive *"PTR_FORMAT" = "PTR_FORMAT" "PTR_FORMAT, - p, (void*) obj, (void*) *p); - } -#endif // G1_DEBUG - if (_g1->obj_in_cs(obj)) { assert( obj->is_forwarded(), "invariant" ); *p = obj->forwardee(); -#ifdef G1_DEBUG - gclog_or_tty->print_cr(" in CSet: moved "PTR_FORMAT" -> "PTR_FORMAT, - (void*) obj, (void*) *p); -#endif // G1_DEBUG } } }; diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/g1CollectorPolicy.cpp --- a/src/share/vm/gc_implementation/g1/g1CollectorPolicy.cpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/g1CollectorPolicy.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -28,6 +28,7 @@ #include "gc_implementation/g1/concurrentMarkThread.inline.hpp" #include "gc_implementation/g1/g1CollectedHeap.inline.hpp" #include "gc_implementation/g1/g1CollectorPolicy.hpp" +#include "gc_implementation/g1/g1ErgoVerbose.hpp" #include "gc_implementation/g1/heapRegionRemSet.hpp" #include "gc_implementation/shared/gcPolicyCounters.hpp" #include "runtime/arguments.hpp" @@ -271,15 +272,26 @@ _recorded_survivor_tail(NULL), _survivors_age_table(true), - _gc_overhead_perc(0.0) - -{ + _gc_overhead_perc(0.0) { + // Set up the region size and associated fields. Given that the // policy is created before the heap, we have to set this up here, // so it's done as soon as possible. HeapRegion::setup_heap_region_size(Arguments::min_heap_size()); HeapRegionRemSet::setup_remset_size(); + G1ErgoVerbose::initialize(); + if (PrintAdaptiveSizePolicy) { + // Currently, we only use a single switch for all the heuristics. + G1ErgoVerbose::set_enabled(true); + // Given that we don't currently have a verboseness level + // parameter, we'll hardcode this to high. This can be easily + // changed in the future. + G1ErgoVerbose::set_level(ErgoHigh); + } else { + G1ErgoVerbose::set_enabled(false); + } + // Verify PLAB sizes const uint region_size = HeapRegion::GrainWords; if (YoungPLABSize > region_size || OldPLABSize > region_size) { @@ -959,11 +971,9 @@ G1CollectorPolicy:: record_concurrent_mark_cleanup_end_work1(size_t freed_bytes, size_t max_live_bytes) { - if (_n_marks < 2) _n_marks++; - if (G1PolicyVerbose > 0) - gclog_or_tty->print_cr("At end of marking, max_live is " SIZE_FORMAT " MB " - " (of " SIZE_FORMAT " MB heap).", - max_live_bytes/M, _g1->capacity()/M); + if (_n_marks < 2) { + _n_marks++; + } } // The important thing about this is that it includes "os::elapsedTime". @@ -977,14 +987,6 @@ _mmu_tracker->add_pause(_mark_cleanup_start_sec, end_time_sec, true); _num_markings++; - - // We did a marking, so reset the "since_last_mark" variables. - double considerConcMarkCost = 1.0; - // If there are available processors, concurrent activity is free... - if (Threads::number_of_non_daemon_threads() * 2 < - os::active_processor_count()) { - considerConcMarkCost = 0.0; - } _n_pauses_at_mark_end = _n_pauses; _n_marks_since_last_pause++; } @@ -1148,20 +1150,37 @@ if (last_pause_included_initial_mark) record_concurrent_mark_init_end(0.0); - size_t min_used_targ = + size_t marking_initiating_used_threshold = (_g1->capacity() / 100) * InitiatingHeapOccupancyPercent; - if (!_g1->mark_in_progress() && !_last_full_young_gc) { assert(!last_pause_included_initial_mark, "invariant"); - if (cur_used_bytes > min_used_targ && - cur_used_bytes > _prev_collection_pause_used_at_end_bytes) { + if (cur_used_bytes > marking_initiating_used_threshold) { + if (cur_used_bytes > _prev_collection_pause_used_at_end_bytes) { assert(!during_initial_mark_pause(), "we should not see this here"); + ergo_verbose3(ErgoConcCycles, + "request concurrent cycle initiation", + ergo_format_reason("occupancy higher than threshold") + ergo_format_byte("occupancy") + ergo_format_byte_perc("threshold"), + cur_used_bytes, + marking_initiating_used_threshold, + (double) InitiatingHeapOccupancyPercent); + // Note: this might have already been set, if during the last // pause we decided to start a cycle but at the beginning of // this pause we decided to postpone it. That's OK. set_initiate_conc_mark_if_possible(); + } else { + ergo_verbose2(ErgoConcCycles, + "do not request concurrent cycle initiation", + ergo_format_reason("occupancy lower than previous occupancy") + ergo_format_byte("occupancy") + ergo_format_byte("previous occupancy"), + cur_used_bytes, + _prev_collection_pause_used_at_end_bytes); + } } } @@ -1437,16 +1456,45 @@ } if (_last_full_young_gc) { + ergo_verbose2(ErgoPartiallyYoungGCs, + "start partially-young GCs", + ergo_format_byte_perc("known garbage"), + _known_garbage_bytes, _known_garbage_ratio * 100.0); set_full_young_gcs(false); _last_full_young_gc = false; } if ( !_last_young_gc_full ) { - if ( _should_revert_to_full_young_gcs || - _known_garbage_ratio < 0.05 || - (adaptive_young_list_length() && - (get_gc_eff_factor() * cur_efficiency < predict_young_gc_eff())) ) { - set_full_young_gcs(true); + if (_should_revert_to_full_young_gcs) { + ergo_verbose2(ErgoPartiallyYoungGCs, + "end partially-young GCs", + ergo_format_reason("partially-young GCs end requested") + ergo_format_byte_perc("known garbage"), + _known_garbage_bytes, _known_garbage_ratio * 100.0); + set_full_young_gcs(true); + } else if (_known_garbage_ratio < 0.05) { + ergo_verbose3(ErgoPartiallyYoungGCs, + "end partially-young GCs", + ergo_format_reason("known garbage percent lower than threshold") + ergo_format_byte_perc("known garbage") + ergo_format_perc("threshold"), + _known_garbage_bytes, _known_garbage_ratio * 100.0, + 0.05 * 100.0); + set_full_young_gcs(true); + } else if (adaptive_young_list_length() && + (get_gc_eff_factor() * cur_efficiency < predict_young_gc_eff())) { + ergo_verbose5(ErgoPartiallyYoungGCs, + "end partially-young GCs", + ergo_format_reason("current GC efficiency lower than " + "predicted fully-young GC efficiency") + ergo_format_double("GC efficiency factor") + ergo_format_double("current GC efficiency") + ergo_format_double("predicted fully-young GC efficiency") + ergo_format_byte_perc("known garbage"), + get_gc_eff_factor(), cur_efficiency, + predict_young_gc_eff(), + _known_garbage_bytes, _known_garbage_ratio * 100.0); + set_full_young_gcs(true); } } _should_revert_to_full_young_gcs = false; @@ -1877,6 +1925,12 @@ // I don't think we need to do this when in young GC mode since // marking will be initiated next time we hit the soft limit anyway... if (predicted_time_ms > _expensive_region_limit_ms) { + ergo_verbose2(ErgoPartiallyYoungGCs, + "request partially-young GCs end", + ergo_format_reason("predicted region time higher than threshold") + ergo_format_ms("predicted region time") + ergo_format_ms("threshold"), + predicted_time_ms, _expensive_region_limit_ms); // no point in doing another partial one _should_revert_to_full_young_gcs = true; } @@ -1986,7 +2040,9 @@ } size_t G1CollectorPolicy::expansion_amount() { - if ((recent_avg_pause_time_ratio() * 100.0) > _gc_overhead_perc) { + double recent_gc_overhead = recent_avg_pause_time_ratio() * 100.0; + double threshold = _gc_overhead_perc; + if (recent_gc_overhead > threshold) { // We will double the existing space, or take // G1ExpandByPercentOfAvailable % of the available expansion // space, whichever is smaller, bounded below by a minimum @@ -2001,20 +2057,19 @@ expand_bytes = MIN2(expand_bytes_via_pct, committed_bytes); expand_bytes = MAX2(expand_bytes, min_expand_bytes); expand_bytes = MIN2(expand_bytes, uncommitted_bytes); - if (G1PolicyVerbose > 1) { - gclog_or_tty->print("Decided to expand: ratio = %5.2f, " - "committed = %d%s, uncommited = %d%s, via pct = %d%s.\n" - " Answer = %d.\n", - recent_avg_pause_time_ratio(), - byte_size_in_proper_unit(committed_bytes), - proper_unit_for_byte_size(committed_bytes), - byte_size_in_proper_unit(uncommitted_bytes), - proper_unit_for_byte_size(uncommitted_bytes), - byte_size_in_proper_unit(expand_bytes_via_pct), - proper_unit_for_byte_size(expand_bytes_via_pct), - byte_size_in_proper_unit(expand_bytes), - proper_unit_for_byte_size(expand_bytes)); - } + + ergo_verbose5(ErgoHeapSizing, + "attempt heap expansion", + ergo_format_reason("recent GC overhead higher than " + "threshold after GC") + ergo_format_perc("recent GC overhead") + ergo_format_perc("threshold") + ergo_format_byte("uncommitted") + ergo_format_byte_perc("calculated expansion amount"), + recent_gc_overhead, threshold, + uncommitted_bytes, + expand_bytes_via_pct, (double) G1ExpandByPercentOfAvailable); + return expand_bytes; } else { return 0; @@ -2237,8 +2292,7 @@ #endif // PRODUCT } -void -G1CollectorPolicy::update_region_num(bool young) { +void G1CollectorPolicy::update_region_num(bool young) { if (young) { ++_region_num_young; } else { @@ -2315,13 +2369,23 @@ } #endif -bool -G1CollectorPolicy::force_initial_mark_if_outside_cycle() { +bool G1CollectorPolicy::force_initial_mark_if_outside_cycle( + GCCause::Cause gc_cause) { bool during_cycle = _g1->concurrent_mark()->cmThread()->during_cycle(); if (!during_cycle) { + ergo_verbose1(ErgoConcCycles, + "request concurrent cycle initiation", + ergo_format_reason("requested by GC cause") + ergo_format_str("GC cause"), + GCCause::to_string(gc_cause)); set_initiate_conc_mark_if_possible(); return true; } else { + ergo_verbose1(ErgoConcCycles, + "do not request concurrent cycle initiation", + ergo_format_reason("concurrent cycle already in progress") + ergo_format_str("GC cause"), + GCCause::to_string(gc_cause)); return false; } } @@ -2353,6 +2417,10 @@ // And we can now clear initiate_conc_mark_if_possible() as // we've already acted on it. clear_initiate_conc_mark_if_possible(); + + ergo_verbose0(ErgoConcCycles, + "initiate concurrent cycle", + ergo_format_reason("concurrent cycle initiation requested")); } else { // The concurrent marking thread is still finishing up the // previous cycle. If we start one right now the two cycles @@ -2366,6 +2434,9 @@ // and, if it's in a yield point, it's waiting for us to // finish. So, at this point we will not start a cycle and we'll // let the concurrent marking thread complete the last one. + ergo_verbose0(ErgoConcCycles, + "do not initiate concurrent cycle", + ergo_format_reason("concurrent cycle already in progress")); } } } @@ -2756,6 +2827,8 @@ // Set this here - in case we're not doing young collections. double non_young_start_time_sec = os::elapsedTime(); + YoungList* young_list = _g1->young_list(); + start_recording_regions(); guarantee(target_pause_time_ms > 0.0, @@ -2768,61 +2841,62 @@ double time_remaining_ms = target_pause_time_ms - base_time_ms; + ergo_verbose3(ErgoCSetConstruction | ErgoHigh, + "start choosing CSet", + ergo_format_ms("predicted base time") + ergo_format_ms("remaining time") + ergo_format_ms("target pause time"), + base_time_ms, time_remaining_ms, target_pause_time_ms); + // the 10% and 50% values are arbitrary... - if (time_remaining_ms < 0.10 * target_pause_time_ms) { + double threshold = 0.10 * target_pause_time_ms; + if (time_remaining_ms < threshold) { + double prev_time_remaining_ms = time_remaining_ms; time_remaining_ms = 0.50 * target_pause_time_ms; _within_target = false; + ergo_verbose3(ErgoCSetConstruction, + "adjust remaining time", + ergo_format_reason("remaining time lower than threshold") + ergo_format_ms("remaining time") + ergo_format_ms("threshold") + ergo_format_ms("adjusted remaining time"), + prev_time_remaining_ms, threshold, time_remaining_ms); } else { _within_target = true; } - // We figure out the number of bytes available for future to-space. - // For new regions without marking information, we must assume the - // worst-case of complete survival. If we have marking information for a - // region, we can bound the amount of live data. We can add a number of - // such regions, as long as the sum of the live data bounds does not - // exceed the available evacuation space. - size_t max_live_bytes = _g1->free_regions() * HeapRegion::GrainBytes; - - size_t expansion_bytes = - _g1->expansion_regions() * HeapRegion::GrainBytes; + size_t expansion_bytes = _g1->expansion_regions() * HeapRegion::GrainBytes; + + HeapRegion* hr; + double young_start_time_sec = os::elapsedTime(); _collection_set_bytes_used_before = 0; _collection_set_size = 0; - - // Adjust for expansion and slop. - max_live_bytes = max_live_bytes + expansion_bytes; - - HeapRegion* hr; - double young_start_time_sec = os::elapsedTime(); - - if (G1PolicyVerbose > 0) { - gclog_or_tty->print_cr("Adding %d young regions to the CSet", - _g1->young_list()->length()); - } - _young_cset_length = 0; _last_young_gc_full = full_young_gcs() ? true : false; - if (_last_young_gc_full) + if (_last_young_gc_full) { ++_full_young_pause_num; - else + } else { ++_partial_young_pause_num; + } // The young list is laid with the survivor regions from the previous // pause are appended to the RHS of the young list, i.e. // [Newly Young Regions ++ Survivors from last pause]. - hr = _g1->young_list()->first_survivor_region(); + size_t survivor_region_num = young_list->survivor_length(); + size_t eden_region_num = young_list->length() - survivor_region_num; + size_t old_region_num = 0; + hr = young_list->first_survivor_region(); while (hr != NULL) { assert(hr->is_survivor(), "badly formed young list"); hr->set_young(); hr = hr->get_next_young_region(); } - // Clear the fields that point to the survivor list - they are - // all young now. - _g1->young_list()->clear_survivors(); + // Clear the fields that point to the survivor list - they are all young now. + young_list->clear_survivors(); if (_g1->mark_in_progress()) _g1->concurrent_mark()->register_collection_set_finger(_inc_cset_max_finger); @@ -2831,14 +2905,17 @@ _collection_set = _inc_cset_head; _collection_set_size = _inc_cset_size; _collection_set_bytes_used_before = _inc_cset_bytes_used_before; - - // For young regions in the collection set, we assume the worst - // case of complete survival - max_live_bytes -= _inc_cset_size * HeapRegion::GrainBytes; - time_remaining_ms -= _inc_cset_predicted_elapsed_time_ms; predicted_pause_time_ms += _inc_cset_predicted_elapsed_time_ms; + ergo_verbose3(ErgoCSetConstruction | ErgoHigh, + "add young regions to CSet", + ergo_format_region("eden") + ergo_format_region("survivors") + ergo_format_ms("predicted young region time"), + eden_region_num, survivor_region_num, + _inc_cset_predicted_elapsed_time_ms); + // The number of recorded young regions is the incremental // collection set's current size set_recorded_young_regions(_inc_cset_size); @@ -2848,14 +2925,7 @@ set_predicted_bytes_to_copy(_inc_cset_predicted_bytes_to_copy); #endif // PREDICTIONS_VERBOSE - if (G1PolicyVerbose > 0) { - gclog_or_tty->print_cr(" Added " PTR_FORMAT " Young Regions to CS.", - _inc_cset_size); - gclog_or_tty->print_cr(" (" SIZE_FORMAT " KB left in heap.)", - max_live_bytes/K); - } - - assert(_inc_cset_size == _g1->young_list()->length(), "Invariant"); + assert(_inc_cset_size == young_list->length(), "Invariant"); double young_end_time_sec = os::elapsedTime(); _recorded_young_cset_choice_time_ms = @@ -2869,6 +2939,8 @@ NumberSeq seq; double avg_prediction = 100000000000000000.0; // something very large + size_t prev_collection_set_size = _collection_set_size; + double prev_predicted_pause_time_ms = predicted_pause_time_ms; do { hr = _collectionSetChooser->getNextMarkedRegion(time_remaining_ms, avg_prediction); @@ -2878,23 +2950,58 @@ predicted_pause_time_ms += predicted_time_ms; add_to_collection_set(hr); record_non_young_cset_region(hr); - max_live_bytes -= MIN2(hr->max_live_bytes(), max_live_bytes); - if (G1PolicyVerbose > 0) { - gclog_or_tty->print_cr(" (" SIZE_FORMAT " KB left in heap.)", - max_live_bytes/K); - } seq.add(predicted_time_ms); avg_prediction = seq.avg() + seq.sd(); } - should_continue = - ( hr != NULL) && - ( (adaptive_young_list_length()) ? time_remaining_ms > 0.0 - : _collection_set_size < _young_list_fixed_length ); + + should_continue = true; + if (hr == NULL) { + // No need for an ergo verbose message here, + // getNextMarkRegion() does this when it returns NULL. + should_continue = false; + } else { + if (adaptive_young_list_length()) { + if (time_remaining_ms < 0.0) { + ergo_verbose1(ErgoCSetConstruction, + "stop adding old regions to CSet", + ergo_format_reason("remaining time is lower than 0") + ergo_format_ms("remaining time"), + time_remaining_ms); + should_continue = false; + } + } else { + if (_collection_set_size < _young_list_fixed_length) { + ergo_verbose2(ErgoCSetConstruction, + "stop adding old regions to CSet", + ergo_format_reason("CSet length lower than target") + ergo_format_region("CSet") + ergo_format_region("young target"), + _collection_set_size, _young_list_fixed_length); + should_continue = false; + } + } + } } while (should_continue); if (!adaptive_young_list_length() && - _collection_set_size < _young_list_fixed_length) + _collection_set_size < _young_list_fixed_length) { + ergo_verbose2(ErgoCSetConstruction, + "request partially-young GCs end", + ergo_format_reason("CSet length lower than target") + ergo_format_region("CSet") + ergo_format_region("young target"), + _collection_set_size, _young_list_fixed_length); _should_revert_to_full_young_gcs = true; + } + + old_region_num = _collection_set_size - prev_collection_set_size; + + ergo_verbose2(ErgoCSetConstruction | ErgoHigh, + "add old regions to CSet", + ergo_format_region("old") + ergo_format_ms("predicted old region time"), + old_region_num, + predicted_pause_time_ms - prev_predicted_pause_time_ms); } stop_incremental_cset_building(); @@ -2903,6 +3010,16 @@ end_recording_regions(); + ergo_verbose5(ErgoCSetConstruction, + "finish choosing CSet", + ergo_format_region("eden") + ergo_format_region("survivors") + ergo_format_region("old") + ergo_format_ms("predicted pause time") + ergo_format_ms("target pause time"), + eden_region_num, survivor_region_num, old_region_num, + predicted_pause_time_ms, target_pause_time_ms); + double non_young_end_time_sec = os::elapsedTime(); _recorded_non_young_cset_choice_time_ms = (non_young_end_time_sec - non_young_start_time_sec) * 1000.0; @@ -2914,12 +3031,6 @@ } void G1CollectorPolicy_BestRegionsFirst:: -expand_if_possible(size_t numRegions) { - size_t expansion_bytes = numRegions * HeapRegion::GrainBytes; - _g1->expand(expansion_bytes); -} - -void G1CollectorPolicy_BestRegionsFirst:: record_collection_pause_end() { G1CollectorPolicy::record_collection_pause_end(); assert(assertMarkedBytesDataOK(), "Marked regions not OK at pause end."); diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/g1CollectorPolicy.hpp --- a/src/share/vm/gc_implementation/g1/g1CollectorPolicy.hpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/g1CollectorPolicy.hpp Wed Sep 07 12:21:23 2011 -0400 @@ -493,7 +493,6 @@ // -public: void cset_regions_freed() { bool propagate = _last_young_gc_full && !_in_marking_window; _short_lived_surv_rate_group->all_surviving_words_recorded(propagate); @@ -1045,7 +1044,7 @@ // new cycle, as long as we are not already in one. It's best if it // is called during a safepoint when the test whether a cycle is in // progress or not is stable. - bool force_initial_mark_if_outside_cycle(); + bool force_initial_mark_if_outside_cycle(GCCause::Cause gc_cause); // This is called at the very beginning of an evacuation pause (it // has to be the first thing that the pause does). If @@ -1234,8 +1233,6 @@ class G1CollectorPolicy_BestRegionsFirst: public G1CollectorPolicy { CollectionSetChooser* _collectionSetChooser; - // If the estimated is less then desirable, resize if possible. - void expand_if_possible(size_t numRegions); virtual void choose_collection_set(double target_pause_time_ms); virtual void record_collection_pause_start(double start_time_sec, @@ -1269,8 +1266,4 @@ return (sum_of_squares - 2.0 * avg * sum + n_d * avg * avg) / n_d; } -// Local Variables: *** -// c-indentation-style: gnu *** -// End: *** - #endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1COLLECTORPOLICY_HPP diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/g1ErgoVerbose.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/vm/gc_implementation/g1/g1ErgoVerbose.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2011, 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. + * + */ + +#include "precompiled.hpp" +#include "gc_implementation/g1/g1ErgoVerbose.hpp" +#include "utilities/ostream.hpp" + +ErgoLevel G1ErgoVerbose::_level; +bool G1ErgoVerbose::_enabled[ErgoHeuristicNum]; + +void G1ErgoVerbose::initialize() { + set_level(ErgoLow); + set_enabled(false); +} + +void G1ErgoVerbose::set_level(ErgoLevel level) { + _level = level; +} + +void G1ErgoVerbose::set_enabled(ErgoHeuristic n, bool enabled) { + assert(0 <= n && n < ErgoHeuristicNum, "pre-condition"); + _enabled[n] = enabled; +} + +void G1ErgoVerbose::set_enabled(bool enabled) { + for (int n = 0; n < ErgoHeuristicNum; n += 1) { + set_enabled((ErgoHeuristic) n, enabled); + } +} + +const char* G1ErgoVerbose::to_string(int tag) { + ErgoHeuristic n = extract_heuristic(tag); + switch (n) { + case ErgoHeapSizing: return "Heap Sizing"; + case ErgoCSetConstruction: return "CSet Construction"; + case ErgoConcCycles: return "Concurrent Cycles"; + case ErgoPartiallyYoungGCs: return "Partially-Young GCs"; + default: + ShouldNotReachHere(); + // Keep the Windows compiler happy + return NULL; + } +} + diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/g1ErgoVerbose.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/vm/gc_implementation/g1/g1ErgoVerbose.hpp Wed Sep 07 12:21:23 2011 -0400 @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2011, 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. + * + */ + +#ifndef SHARE_VM_GC_IMPLEMENTATION_G1_G1ERGOVERBOSE_HPP +#define SHARE_VM_GC_IMPLEMENTATION_G1_G1ERGOVERBOSE_HPP + +#include "memory/allocation.hpp" +#include "utilities/debug.hpp" + +// The log of G1's heuristic decisions comprises of a series of +// records which have a similar format in order to maintain +// consistency across records and ultimately easier parsing of the +// output, if we ever choose to do that. Each record consists of: +// * A time stamp to be able to easily correlate each record with +// other events. +// * A unique string to allow us to easily identify such records. +// * The name of the heuristic the record corresponds to. +// * An action string which describes the action that G1 did or is +// about to do. +// * An optional reason string which describes the reason for the +// action. +// * An optional number of name/value pairs which contributed to the +// decision to take the action described in the record. +// +// Each record is associated with a "tag" which is the combination of +// the heuristic the record corresponds to, as well as the min level +// of verboseness at which the record should be printed. The tag is +// checked against the current settings to determine whether the record +// should be printed or not. + +// The available verboseness levels. +typedef enum { + // Determine which part of the tag is occupied by the level. + ErgoLevelShift = 8, + ErgoLevelMask = ~((1 << ErgoLevelShift) - 1), + + // ErgoLow is 0 so that we don't have to explicitly or a heuristic + // id with ErgoLow to keep its use simpler. + ErgoLow = 0, + ErgoHigh = 1 << ErgoLevelShift, +} ErgoLevel; + +// The available heuristics. +typedef enum { + // Determines which part of the tag is occupied by the heuristic id. + ErgoHeuristicMask = ~ErgoLevelMask, + + ErgoHeapSizing = 0, + ErgoCSetConstruction, + ErgoConcCycles, + ErgoPartiallyYoungGCs, + + ErgoHeuristicNum +} ErgoHeuristic; + +class G1ErgoVerbose : AllStatic { +private: + // Determines the minimum verboseness level at which records will be + // printed. + static ErgoLevel _level; + // Determines which heuristics are currently enabled. + static bool _enabled[ErgoHeuristicNum]; + + static ErgoLevel extract_level(int tag) { + return (ErgoLevel) (tag & ErgoLevelMask); + } + + static ErgoHeuristic extract_heuristic(int tag) { + return (ErgoHeuristic) (tag & ErgoHeuristicMask); + } + +public: + // Needs to be explicitly called at GC initialization. + static void initialize(); + + static void set_level(ErgoLevel level); + static void set_enabled(ErgoHeuristic h, bool enabled); + // It is applied to all heuristics. + static void set_enabled(bool enabled); + + static bool enabled(int tag) { + ErgoLevel level = extract_level(tag); + ErgoHeuristic n = extract_heuristic(tag); + return level <= _level && _enabled[n]; + } + + // Extract the heuristic id from the tag and return a string with + // its name. + static const char* to_string(int tag); +}; + +// The macros below generate the format string for values of different +// types and/or metrics. + +// The reason for the action is optional and is handled specially: the +// reason string is concatenated here so it's not necessary to pass it +// as a parameter. +#define ergo_format_reason(_reason_) ", reason: " _reason_ + +// Single parameter format strings +#define ergo_format_str(_name_) ", " _name_ ": %s" +#define ergo_format_region(_name_) ", " _name_ ": "SIZE_FORMAT" regions" +#define ergo_format_byte(_name_) ", " _name_ ": "SIZE_FORMAT" bytes" +#define ergo_format_double(_name_) ", " _name_ ": %1.2f" +#define ergo_format_perc(_name_) ", " _name_ ": %1.2f %%" +#define ergo_format_ms(_name_) ", " _name_ ": %1.2f ms" + +// Double parameter format strings +#define ergo_format_byte_perc(_name_) \ + ", " _name_ ": "SIZE_FORMAT" bytes (%1.2f %%)" + +// Generates the format string +#define ergo_format(_action_, _extra_format_) \ + " %1.3f: [G1Ergonomics (%s) " _action_ _extra_format_ "]" + +// Conditionally, prints an ergonomic decision record. _extra_format_ +// is the format string for the optional items we'd like to print +// (i.e., the decision's reason and any associated values). This +// string should be built up using the ergo_*_format macros (see +// above) to ensure consistency. +// +// Since we cannot rely on the compiler supporting variable argument +// macros, this macro accepts a fixed number of arguments and passes +// them to the print method. For convenience, we have wrapper macros +// below which take a specific number of arguments and set the rest to +// a default value. +#define ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_, _arg4_, _arg5_) \ + do { \ + if (G1ErgoVerbose::enabled((_tag_))) { \ + gclog_or_tty->print_cr(ergo_format(_action_, _extra_format_), \ + os::elapsedTime(), \ + G1ErgoVerbose::to_string((_tag_)), \ + (_arg0_), (_arg1_), (_arg2_), \ + (_arg3_), (_arg4_), (_arg5_)); \ + } \ + } while (0) + + +#define ergo_verbose(_tag_, _action_) \ + ergo_verbose_common(_tag_, _action_, "", 0, 0, 0, 0, 0, 0) + +#define ergo_verbose0(_tag_, _action_, _extra_format_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, 0, 0, 0, 0, 0, 0) + +#define ergo_verbose1(_tag_, _action_, _extra_format_, \ + _arg0_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, 0, 0, 0, 0, 0) + +#define ergo_verbose2(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, 0, 0, 0, 0) + +#define ergo_verbose3(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, 0, 0, 0) + +#define ergo_verbose4(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_, 0, 0) + +#define ergo_verbose5(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_, _arg4_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_, _arg4_, 0) + +#define ergo_verbose6(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_, _arg4_, _arg5_) \ + ergo_verbose_common(_tag_, _action_, _extra_format_, \ + _arg0_, _arg1_, _arg2_, _arg3_, _arg4_, _arg5_) + +#endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1ERGOVERBOSE_HPP diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/g1MMUTracker.cpp --- a/src/share/vm/gc_implementation/g1/g1MMUTracker.cpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/g1MMUTracker.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2011, 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 @@ -97,10 +97,6 @@ // or performance (we are GC'ing most of the time anyway!), // simply overwrite the oldest entry in the tracker. - if (G1PolicyVerbose > 1) { - warning("MMU Tracker Queue overflow. Replacing earliest entry."); - } - _head_index = trim_index(_head_index + 1); assert(_head_index == _tail_index, "Because we have a full circular buffer"); _tail_index = trim_index(_tail_index + 1); diff -r 27702f012017 -r 20213c8a3c40 src/share/vm/gc_implementation/g1/vm_operations_g1.cpp --- a/src/share/vm/gc_implementation/g1/vm_operations_g1.cpp Tue Sep 06 21:03:51 2011 -0700 +++ b/src/share/vm/gc_implementation/g1/vm_operations_g1.cpp Wed Sep 07 12:21:23 2011 -0400 @@ -98,7 +98,7 @@ // At this point we are supposed to start a concurrent cycle. We // will do so if one is not already in progress. - bool res = g1h->g1_policy()->force_initial_mark_if_outside_cycle(); + bool res = g1h->g1_policy()->force_initial_mark_if_outside_cycle(_gc_cause); // The above routine returns true if we were able to force the // next GC pause to be an initial mark; it returns false if a