8212657: Implementation of JDK-8204089 Promptly Return Unused Committed Memory from G1

Issue optional, default enabled, concurrent cycles when the VM is idle to reclaim unused internal and Java heap memory.

Co-authored-by: Rodrigo Bruno <rbruno@gsd.inesc-id.pt>
Co-authored-by: Ruslan Synytsky <rs@jelastic.com>
Reviewed-by: sjohanss, sangheki
This commit is contained in:
Thomas Schatzl 2018-12-10 10:25:27 +01:00
parent 044dd119da
commit 49efeb69ff
11 changed files with 217 additions and 18 deletions

View File

@ -1150,6 +1150,8 @@ void G1CollectedHeap::do_full_collection(bool clear_all_soft_refs) {
}
void G1CollectedHeap::resize_heap_if_necessary() {
assert_at_safepoint_on_vm_thread();
// Capacity, free and used after the GC counted as full regions to
// include the waste in the following calculations.
const size_t capacity_after_gc = capacity();
@ -1991,6 +1993,7 @@ bool G1CollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {
switch (cause) {
case GCCause::_gc_locker: return GCLockerInvokesConcurrent;
case GCCause::_g1_humongous_allocation: return true;
case GCCause::_g1_periodic_collection: return G1PeriodicGCInvokesConcurrent;
default: return is_user_requested_concurrent_full_gc(cause);
}
}

View File

@ -714,20 +714,34 @@ void G1Policy::record_collection_pause_end(double pause_time_ms, size_t cards_sc
}
_free_regions_at_end_of_collection = _g1h->num_free_regions();
// IHOP control wants to know the expected young gen length if it were not
// restrained by the heap reserve. Using the actual length would make the
// prediction too small and the limit the young gen every time we get to the
// predicted target occupancy.
size_t last_unrestrained_young_length = update_young_list_max_and_target_length();
update_rs_lengths_prediction();
update_ihop_prediction(app_time_ms / 1000.0,
_bytes_allocated_in_old_since_last_gc,
last_unrestrained_young_length * HeapRegion::GrainBytes,
this_pause_was_young_only);
_bytes_allocated_in_old_since_last_gc = 0;
// Do not update dynamic IHOP due to G1 periodic collection as it is highly likely
// that in this case we are not running in a "normal" operating mode.
if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
// IHOP control wants to know the expected young gen length if it were not
// restrained by the heap reserve. Using the actual length would make the
// prediction too small and the limit the young gen every time we get to the
// predicted target occupancy.
size_t last_unrestrained_young_length = update_young_list_max_and_target_length();
_ihop_control->send_trace_event(_g1h->gc_tracer_stw());
update_ihop_prediction(app_time_ms / 1000.0,
_bytes_allocated_in_old_since_last_gc,
last_unrestrained_young_length * HeapRegion::GrainBytes,
this_pause_was_young_only);
_bytes_allocated_in_old_since_last_gc = 0;
_ihop_control->send_trace_event(_g1h->gc_tracer_stw());
} else {
// Any garbage collection triggered as periodic collection resets the time-to-mixed
// measurement. Periodic collection typically means that the application is "inactive", i.e.
// the marking threads may have received an uncharacterisic amount of cpu time
// 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.
_initial_mark_to_mixed.reset();
}
// Note that _mmu_tracker->max_gc_time() returns the time in seconds.
double update_rs_time_goal_ms = _mmu_tracker->max_gc_time() * MILLIUNITS * G1RSetUpdatingPauseTimePercent / 100.0;
@ -1078,7 +1092,9 @@ void G1Policy::record_pause(PauseKind kind, double start, double end) {
_initial_mark_to_mixed.add_pause(end - start);
break;
case InitialMarkGC:
_initial_mark_to_mixed.record_initial_mark_end(end);
if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
_initial_mark_to_mixed.record_initial_mark_end(end);
}
break;
case MixedGC:
_initial_mark_to_mixed.record_mixed_gc_start(start);

View File

@ -25,6 +25,8 @@
#include "precompiled.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1CollectionSet.hpp"
#include "gc/g1/g1ConcurrentMark.inline.hpp"
#include "gc/g1/g1ConcurrentMarkThread.inline.hpp"
#include "gc/g1/g1Policy.hpp"
#include "gc/g1/g1YoungRemSetSamplingThread.hpp"
#include "gc/g1/heapRegion.inline.hpp"
@ -37,7 +39,8 @@ G1YoungRemSetSamplingThread::G1YoungRemSetSamplingThread() :
_monitor(Mutex::nonleaf,
"G1YoungRemSetSamplingThread monitor",
true,
Monitor::_safepoint_check_never) {
Monitor::_safepoint_check_never),
_last_periodic_gc_attempt_s(os::elapsedTime()) {
set_name("G1 Young RemSet Sampling");
create_and_start();
}
@ -45,11 +48,49 @@ G1YoungRemSetSamplingThread::G1YoungRemSetSamplingThread() :
void G1YoungRemSetSamplingThread::sleep_before_next_cycle() {
MutexLockerEx x(&_monitor, Mutex::_no_safepoint_check_flag);
if (!should_terminate()) {
uintx waitms = G1ConcRefinementServiceIntervalMillis; // 300, really should be?
uintx waitms = G1ConcRefinementServiceIntervalMillis;
_monitor.wait(Mutex::_no_safepoint_check_flag, waitms);
}
}
bool G1YoungRemSetSamplingThread::should_start_periodic_gc() {
// If we are currently in a concurrent mark we are going to uncommit memory soon.
if (G1CollectedHeap::heap()->concurrent_mark()->cm_thread()->during_cycle()) {
log_debug(gc, periodic)("Concurrent cycle in progress. Skipping.");
return false;
}
// Check if enough time has passed since the last GC.
uintx time_since_last_gc;
if ((G1PeriodicGCInterval == 0) ||
((time_since_last_gc = (uintx)Universe::heap()->millis_since_last_gc()) < G1PeriodicGCInterval)) {
log_debug(gc, periodic)("Last GC occurred " UINTX_FORMAT "ms before which is below threshold " UINTX_FORMAT "ms. Skipping.",
time_since_last_gc, G1PeriodicGCInterval);
return false;
}
// Check if load is lower than max.
double recent_load;
if ((G1PeriodicGCSystemLoadThreshold > 0) &&
(os::loadavg(&recent_load, 1) == -1 || recent_load > G1PeriodicGCSystemLoadThreshold)) {
log_debug(gc, periodic)("Load %1.2f is higher than threshold " UINTX_FORMAT ". Skipping.",
recent_load, G1PeriodicGCSystemLoadThreshold);
return false;
}
return true;
}
void G1YoungRemSetSamplingThread::check_for_periodic_gc(){
if ((os::elapsedTime() - _last_periodic_gc_attempt_s) > (G1PeriodicGCInterval / 1000.0)) {
log_debug(gc, periodic)("Checking for periodic GC.");
if (should_start_periodic_gc()) {
Universe::heap()->collect(GCCause::_g1_periodic_collection);
}
_last_periodic_gc_attempt_s = os::elapsedTime();
}
}
void G1YoungRemSetSamplingThread::run_service() {
double vtime_start = os::elapsedVTime();
@ -62,6 +103,8 @@ void G1YoungRemSetSamplingThread::run_service() {
_vtime_accum = 0.0;
}
check_for_periodic_gc();
sleep_before_next_cycle();
}
}

View File

@ -43,14 +43,20 @@ class G1YoungRemSetSamplingThread: public ConcurrentGCThread {
private:
Monitor _monitor;
double _last_periodic_gc_attempt_s;
double _vtime_accum; // Accumulated virtual time.
void sample_young_list_rs_lengths();
void run_service();
void check_for_periodic_gc();
void stop_service();
void sleep_before_next_cycle();
double _vtime_accum; // Accumulated virtual time.
bool should_start_periodic_gc();
public:
G1YoungRemSetSamplingThread();

View File

@ -299,6 +299,23 @@
"Verify the code root lists attached to each heap region.") \
\
develop(bool, G1VerifyBitmaps, false, \
"Verifies the consistency of the marking bitmaps")
"Verifies the consistency of the marking bitmaps") \
\
manageable(uintx, G1PeriodicGCInterval, 0, \
"Number of milliseconds after a previous GC to wait before " \
"triggering a periodic gc. A value of zero disables periodically "\
"enforced gc cycles.") \
range(0, max_jlong) \
\
product(bool, G1PeriodicGCInvokesConcurrent, true, \
"Determines the kind of periodic GC. Set to true to have G1 " \
"perform a concurrent GC as periodic GC, otherwise use a STW " \
"Full GC.") \
\
manageable(uintx, G1PeriodicGCSystemLoadThreshold, 0, \
"Maximum recent system wide system load as returned by the 1m " \
"value of getloadavg() at which G1 triggers a periodic GC. A " \
"load above this value cancels a given periodic GC. A value of " \
"zero disables this check.") \
#endif // SHARE_VM_GC_G1_G1_GLOBALS_HPP

View File

@ -105,6 +105,9 @@ const char* GCCause::to_string(GCCause::Cause cause) {
case _g1_humongous_allocation:
return "G1 Humongous Allocation";
case _g1_periodic_collection:
return "G1 Periodic Collection";
case _dcmd_gc_run:
return "Diagnostic Command";

View File

@ -76,6 +76,7 @@ class GCCause : public AllStatic {
_g1_inc_collection_pause,
_g1_humongous_allocation,
_g1_periodic_collection,
_dcmd_gc_run,

View File

@ -81,7 +81,6 @@ bool VM_GC_Operation::skip_operation() const {
}
bool VM_GC_Operation::doit_prologue() {
assert(Thread::current()->is_Java_thread(), "just checking");
assert(((_gc_cause != GCCause::_no_gc) &&
(_gc_cause != GCCause::_no_cause_specified)), "Illegal GCCause");
@ -110,7 +109,6 @@ bool VM_GC_Operation::doit_prologue() {
void VM_GC_Operation::doit_epilogue() {
assert(Thread::current()->is_Java_thread(), "just checking");
// Clean up old interpreter OopMap entries that were replaced
// during the GC thread root traversal.
OopMapCache::cleanup_old_entries();

View File

@ -119,6 +119,7 @@
LOG_TAG(patch) \
LOG_TAG(path) \
LOG_TAG(perf) \
LOG_TAG(periodic) \
LOG_TAG(phases) \
LOG_TAG(plab) \
LOG_TAG(preview) /* Trace loading of preview feature types */ \

View File

@ -178,6 +178,7 @@ tier1_gc_1 = \
gc/epsilon/ \
gc/g1/ \
-gc/g1/ihop/TestIHOPErgo.java
-gc/g1/TestTimelyCompaction.java
tier1_gc_2 = \
gc/ \

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2018, 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.
*/
/**
* @test TestPeriodicCollection
* @requires vm.gc.G1
* @requires vm.compMode != "Xcomp"
* @summary Verify that heap shrinks when the application is idle.
* @library /test/lib /
* @modules java.base/jdk.internal.misc
* @modules java.management/sun.management
* @run main/othervm -XX:MaxNewSize=32M -XX:InitialHeapSize=48M -Xmx128M -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=25 -XX:+UseG1GC -XX:G1PeriodicGCInterval=3000 -XX:+G1PeriodicGCInvokesConcurrent -Xlog:gc,gc+periodic=debug,gc+ergo+heap=debug TestPeriodicCollection
* @run main/othervm -XX:MaxNewSize=32M -XX:InitialHeapSize=48M -Xmx128M -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=25 -XX:+UseG1GC -XX:G1PeriodicGCInterval=3000 -XX:-G1PeriodicGCInvokesConcurrent -Xlog:gc,gc+periodic=debug,gc+ergo+heap=debug TestPeriodicCollection
*/
import com.sun.management.HotSpotDiagnosticMXBean;
import gc.testlibrary.Helpers;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.text.NumberFormat;
import static jdk.test.lib.Asserts.*;
public class TestPeriodicCollection {
public static final String MIN_FREE_RATIO_FLAG_NAME = "MinHeapFreeRatio";
public static final String MAX_FREE_RATIO_FLAG_NAME = "MaxHeapFreeRatio";
private static final int IDLE_TIME = 7 * 1000;
private static boolean gcOccurred() {
for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) {
if (b.getCollectionCount() != 0) {
return true;
}
}
return false;
}
public static void main(String[] args) {
MemoryUsage muInitial = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
MemoryUsagePrinter.printMemoryUsage("initial", muInitial);
if (gcOccurred()) {
System.out.println("At least one garbage collection occurred. Exiting as this may have already shrunk the heap.");
return;
}
try {
Thread.sleep(IDLE_TIME);
} catch (InterruptedException ie) {
System.err.println("Skipped. Failed to wait for idle collection");
}
MemoryUsage muAfter = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
MemoryUsagePrinter.printMemoryUsage("after", muAfter);
assertLessThan(muAfter.getCommitted(), muInitial.getCommitted(), String.format(
"committed free heap size is not less than committed full heap size, heap hasn't been shrunk?%n"
+ "%s = %s%n%s = %s",
MIN_FREE_RATIO_FLAG_NAME,
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
.getVMOption(MIN_FREE_RATIO_FLAG_NAME).getValue(),
MAX_FREE_RATIO_FLAG_NAME,
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
.getVMOption(MAX_FREE_RATIO_FLAG_NAME).getValue()
));
}
}
/**
* Prints memory usage to standard output
*/
class MemoryUsagePrinter {
public static final NumberFormat NF = Helpers.numberFormatter();
public static void printMemoryUsage(String label, MemoryUsage memusage) {
float freeratio = 1f - (float) memusage.getUsed() / memusage.getCommitted();
System.out.format("[%-24s] init: %-7s, used: %-7s, comm: %-7s, freeRatio ~= %.1f%%%n",
label,
NF.format(memusage.getInit()),
NF.format(memusage.getUsed()),
NF.format(memusage.getCommitted()),
freeratio * 100
);
}
}