8195115: G1 Old Gen MemoryPool CollectionUsage.used values don't reflect mixed GC results

Memory pools can now be optional collection participants, e.g., G1 Old Gen in an incremental collection.

Reviewed-by: ehelin, mchung
This commit is contained in:
Paul Hohensee 2018-06-19 05:18:49 -07:00
parent 425e1a8bb8
commit ebe240f3f4
8 changed files with 280 additions and 20 deletions

View File

@ -8088,6 +8088,7 @@ TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorSt
case CMSCollector::InitialMarking:
initialize(manager /* GC manager */ ,
cause /* cause of the GC */,
true /* allMemoryPoolsAffected */,
true /* recordGCBeginTime */,
true /* recordPreGCUsage */,
false /* recordPeakUsage */,
@ -8100,6 +8101,7 @@ TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorSt
case CMSCollector::FinalMarking:
initialize(manager /* GC manager */ ,
cause /* cause of the GC */,
true /* allMemoryPoolsAffected */,
false /* recordGCBeginTime */,
false /* recordPreGCUsage */,
false /* recordPeakUsage */,
@ -8112,6 +8114,7 @@ TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorSt
case CMSCollector::Sweeping:
initialize(manager /* GC manager */ ,
cause /* cause of the GC */,
true /* allMemoryPoolsAffected */,
false /* recordGCBeginTime */,
false /* recordPreGCUsage */,
true /* recordPeakUsage */,

View File

@ -1737,7 +1737,7 @@ void G1CollectedHeap::initialize_serviceability() {
_memory_manager.add_pool(_eden_pool);
_memory_manager.add_pool(_survivor_pool);
_memory_manager.add_pool(_old_pool, false /* always_affected_by_gc */);
}
void G1CollectedHeap::stop() {
@ -2833,7 +2833,8 @@ G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
log_info(gc,task)("Using %u workers of %u for evacuation", active_workers, workers()->total_workers());
TraceCollectorStats tcs(g1mm()->incremental_collection_counters());
TraceMemoryManagerStats tms(&_memory_manager, gc_cause());
TraceMemoryManagerStats tms(&_memory_manager, gc_cause(),
collector_state()->yc_type() == Mixed /* allMemoryPoolsAffected */);
G1HeapTransition heap_transition(this);
size_t heap_used_bytes_before_gc = used();

View File

@ -43,13 +43,15 @@ MemoryManager::MemoryManager(const char* name) : _name(name) {
(void)const_cast<instanceOop&>(_memory_mgr_obj = instanceOop(NULL));
}
void MemoryManager::add_pool(MemoryPool* pool) {
assert(_num_pools < MemoryManager::max_num_pools, "_num_pools exceeds the max");
if (_num_pools < MemoryManager::max_num_pools) {
_pools[_num_pools] = pool;
int MemoryManager::add_pool(MemoryPool* pool) {
int index = _num_pools;
assert(index < MemoryManager::max_num_pools, "_num_pools exceeds the max");
if (index < MemoryManager::max_num_pools) {
_pools[index] = pool;
_num_pools++;
}
pool->add_manager(this);
return index;
}
MemoryManager* MemoryManager::get_code_cache_memory_manager() {
@ -189,6 +191,15 @@ GCMemoryManager::~GCMemoryManager() {
delete _current_gc_stat;
}
void GCMemoryManager::add_pool(MemoryPool* pool) {
add_pool(pool, true);
}
void GCMemoryManager::add_pool(MemoryPool* pool, bool always_affected_by_gc) {
int index = MemoryManager::add_pool(pool);
_pool_always_affected_by_gc[index] = always_affected_by_gc;
}
void GCMemoryManager::initialize_gc_stat_info() {
assert(MemoryService::num_memory_pools() > 0, "should have one or more memory pools");
_last_gc_stat = new(ResourceObj::C_HEAP, mtGC) GCStatInfo(MemoryService::num_memory_pools());
@ -230,7 +241,8 @@ void GCMemoryManager::gc_begin(bool recordGCBeginTime, bool recordPreGCUsage,
void GCMemoryManager::gc_end(bool recordPostGCUsage,
bool recordAccumulatedGCTime,
bool recordGCEndTime, bool countCollection,
GCCause::Cause cause) {
GCCause::Cause cause,
bool allMemoryPoolsAffected) {
if (recordAccumulatedGCTime) {
_accumulated_timer.stop();
}
@ -259,9 +271,11 @@ void GCMemoryManager::gc_end(bool recordPostGCUsage,
MemoryPool* pool = get_memory_pool(i);
MemoryUsage usage = pool->get_memory_usage();
// Compare with GC usage threshold
pool->set_last_collection_usage(usage);
LowMemoryDetector::detect_after_gc_memory(pool);
if (allMemoryPoolsAffected || pool_always_affected_by_gc(i)) {
// Compare with GC usage threshold
pool->set_last_collection_usage(usage);
LowMemoryDetector::detect_after_gc_memory(pool);
}
}
}

View File

@ -45,11 +45,12 @@ class GCMemoryManager;
class OopClosure;
class MemoryManager : public CHeapObj<mtInternal> {
private:
protected:
enum {
max_num_pools = 10
};
private:
MemoryPool* _pools[max_num_pools];
int _num_pools;
@ -67,7 +68,7 @@ public:
return _pools[index];
}
void add_pool(MemoryPool* pool);
int add_pool(MemoryPool* pool);
bool is_manager(instanceHandle mh) { return oopDesc::equals(mh(), _memory_mgr_obj); }
@ -142,11 +143,21 @@ private:
GCStatInfo* _current_gc_stat;
int _num_gc_threads;
volatile bool _notification_enabled;
const char* _gc_end_message;
const char* _gc_end_message;
bool _pool_always_affected_by_gc[MemoryManager::max_num_pools];
public:
GCMemoryManager(const char* name, const char* gc_end_message);
~GCMemoryManager();
void add_pool(MemoryPool* pool);
void add_pool(MemoryPool* pool, bool always_affected_by_gc);
bool pool_always_affected_by_gc(int index) {
assert(index >= 0 && index < num_memory_pools(), "Invalid index");
return _pool_always_affected_by_gc[index];
}
void initialize_gc_stat_info();
bool is_gc_memory_manager() { return true; }
@ -158,7 +169,8 @@ public:
void gc_begin(bool recordGCBeginTime, bool recordPreGCUsage,
bool recordAccumulatedGCTime);
void gc_end(bool recordPostGCUsage, bool recordAccumulatedGCTime,
bool recordGCEndTime, bool countCollection, GCCause::Cause cause);
bool recordGCEndTime, bool countCollection, GCCause::Cause cause,
bool allMemoryPoolsAffected);
void reset_gc_stat() { _num_collections = 0; _accumulated_timer.reset(); }

View File

@ -182,10 +182,11 @@ void MemoryService::gc_begin(GCMemoryManager* manager, bool recordGCBeginTime,
void MemoryService::gc_end(GCMemoryManager* manager, bool recordPostGCUsage,
bool recordAccumulatedGCTime,
bool recordGCEndTime, bool countCollection,
GCCause::Cause cause) {
GCCause::Cause cause,
bool allMemoryPoolsAffected) {
// register the GC end statistics and memory usage
manager->gc_end(recordPostGCUsage, recordAccumulatedGCTime, recordGCEndTime,
countCollection, cause);
countCollection, cause, allMemoryPoolsAffected);
}
void MemoryService::oops_do(OopClosure* f) {
@ -232,6 +233,7 @@ Handle MemoryService::create_MemoryUsage_obj(MemoryUsage usage, TRAPS) {
TraceMemoryManagerStats::TraceMemoryManagerStats(GCMemoryManager* gc_memory_manager,
GCCause::Cause cause,
bool allMemoryPoolsAffected,
bool recordGCBeginTime,
bool recordPreGCUsage,
bool recordPeakUsage,
@ -239,7 +241,8 @@ TraceMemoryManagerStats::TraceMemoryManagerStats(GCMemoryManager* gc_memory_mana
bool recordAccumulatedGCTime,
bool recordGCEndTime,
bool countCollection) {
initialize(gc_memory_manager, cause, recordGCBeginTime, recordPreGCUsage, recordPeakUsage,
initialize(gc_memory_manager, cause, allMemoryPoolsAffected,
recordGCBeginTime, recordPreGCUsage, recordPeakUsage,
recordPostGCUsage, recordAccumulatedGCTime, recordGCEndTime,
countCollection);
}
@ -248,6 +251,7 @@ TraceMemoryManagerStats::TraceMemoryManagerStats(GCMemoryManager* gc_memory_mana
// the MemoryService
void TraceMemoryManagerStats::initialize(GCMemoryManager* gc_memory_manager,
GCCause::Cause cause,
bool allMemoryPoolsAffected,
bool recordGCBeginTime,
bool recordPreGCUsage,
bool recordPeakUsage,
@ -256,6 +260,7 @@ void TraceMemoryManagerStats::initialize(GCMemoryManager* gc_memory_manager,
bool recordGCEndTime,
bool countCollection) {
_gc_memory_manager = gc_memory_manager;
_allMemoryPoolsAffected = allMemoryPoolsAffected;
_recordGCBeginTime = recordGCBeginTime;
_recordPreGCUsage = recordPreGCUsage;
_recordPeakUsage = recordPeakUsage;
@ -271,5 +276,5 @@ void TraceMemoryManagerStats::initialize(GCMemoryManager* gc_memory_manager,
TraceMemoryManagerStats::~TraceMemoryManagerStats() {
MemoryService::gc_end(_gc_memory_manager, _recordPostGCUsage, _recordAccumulatedGCTime,
_recordGCEndTime, _countCollection, _cause);
_recordGCEndTime, _countCollection, _cause, _allMemoryPoolsAffected);
}

View File

@ -102,7 +102,8 @@ public:
static void gc_end(GCMemoryManager* manager, bool recordPostGCUsage,
bool recordAccumulatedGCTime,
bool recordGCEndTime, bool countCollection,
GCCause::Cause cause);
GCCause::Cause cause,
bool allMemoryPoolsAffected);
static void oops_do(OopClosure* f);
@ -116,6 +117,7 @@ public:
class TraceMemoryManagerStats : public StackObj {
private:
GCMemoryManager* _gc_memory_manager;
bool _allMemoryPoolsAffected;
bool _recordGCBeginTime;
bool _recordPreGCUsage;
bool _recordPeakUsage;
@ -128,6 +130,7 @@ public:
TraceMemoryManagerStats() {}
TraceMemoryManagerStats(GCMemoryManager* gc_memory_manager,
GCCause::Cause cause,
bool allMemoryPoolsAffected = true,
bool recordGCBeginTime = true,
bool recordPreGCUsage = true,
bool recordPeakUsage = true,
@ -138,6 +141,7 @@ public:
void initialize(GCMemoryManager* gc_memory_manager,
GCCause::Cause cause,
bool allMemoryPoolsAffected,
bool recordGCBeginTime,
bool recordPreGCUsage,
bool recordPeakUsage,

View File

@ -86,7 +86,7 @@ public class TestMemoryMXBeansAndPoolsPresence {
public static void main(String[] args) {
switch (args[0]) {
case "G1":
test(new GCBeanDescription("G1 Young Generation", new String[] {"G1 Eden Space", "G1 Survivor Space"}),
test(new GCBeanDescription("G1 Young Generation", new String[] {"G1 Eden Space", "G1 Survivor Space", "G1 Old Gen"}),
new GCBeanDescription("G1 Old Generation", new String[] {"G1 Eden Space", "G1 Survivor Space", "G1 Old Gen"}));
break;
case "CMS":

View File

@ -0,0 +1,221 @@
/*
* 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 TestOldGenCollectionUsage.java
* @bug 8195115
* @summary G1 Old Gen's CollectionUsage.used is zero after mixed GC which is incorrect
* @key gc
* @requires vm.gc.G1
* @requires vm.opt.MaxGCPauseMillis == "null"
* @library /test/lib
* @modules java.base/jdk.internal.misc
* @modules java.management
* @build sun.hotspot.WhiteBox
* @run driver ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -verbose:gc -XX:SurvivorRatio=1 -Xmx12m -Xms12m -XX:MaxTenuringThreshold=1 -XX:InitiatingHeapOccupancyPercent=100 -XX:-G1UseAdaptiveIHOP -XX:G1MixedGCCountTarget=4 -XX:MaxGCPauseMillis=30000 -XX:G1HeapRegionSize=1m -XX:G1HeapWastePercent=0 -XX:G1MixedGCLiveThresholdPercent=100 TestOldGenCollectionUsage
*/
import jdk.test.lib.Asserts;
import sun.hotspot.WhiteBox;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.lang.management.*;
// 8195115 says that for the "G1 Old Gen" MemoryPool, CollectionUsage.used
// is zero for G1 after a mixed collection, which is incorrect.
public class TestOldGenCollectionUsage {
private String poolName = "G1 Old Gen";
private String collectorName = "G1 Young Generation";
public static void main(String [] args) throws Exception {
TestOldGenCollectionUsage t = new TestOldGenCollectionUsage();
t.run();
}
public TestOldGenCollectionUsage() {
System.out.println("Monitor G1 Old Gen pool with G1 Young Generation collector.");
}
public void run() {
// Find memory pool and collector
List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
MemoryPoolMXBean pool = null;
boolean foundPool = false;
for (int i = 0; i < pools.size(); i++) {
pool = pools.get(i);
String name = pool.getName();
if (name.contains(poolName)) {
System.out.println("Found pool: " + name);
foundPool = true;
break;
}
}
if (!foundPool) {
throw new RuntimeException(poolName + " not found, test with -XX:+UseG1GC");
}
List<GarbageCollectorMXBean> collectors = ManagementFactory.getGarbageCollectorMXBeans();
GarbageCollectorMXBean collector = null;
boolean foundCollector = false;
for (int i = 0; i < collectors.size(); i++) {
collector = collectors.get(i);
String name = collector.getName();
if (name.contains(collectorName)) {
System.out.println("Found collector: " + name);
foundCollector = true;
break;
}
}
if (!foundCollector) {
throw new RuntimeException(collectorName + " not found, test with -XX:+UseG1GC");
}
MixedGCProvoker gcProvoker = new MixedGCProvoker();
gcProvoker.allocateOldObjects();
// Verify no non-zero result was stored
long usage = pool.getCollectionUsage().getUsed();
System.out.println(poolName + ": usage after GC = " + usage);
if (usage > 0) {
throw new RuntimeException("Premature mixed collections(s)");
}
// Verify that collections were done
long collectionCount = collector.getCollectionCount();
System.out.println(collectorName + ": collection count = "
+ collectionCount);
long collectionTime = collector.getCollectionTime();
System.out.println(collectorName + ": collection time = "
+ collectionTime);
if (collectionCount <= 0) {
throw new RuntimeException("Collection count <= 0");
}
if (collectionTime <= 0) {
throw new RuntimeException("Collector has not run");
}
gcProvoker.provokeMixedGC();
usage = pool.getCollectionUsage().getUsed();
System.out.println(poolName + ": usage after GC = " + usage);
if (usage <= 0) {
throw new RuntimeException(poolName + " found with zero usage");
}
long newCollectionCount = collector.getCollectionCount();
System.out.println(collectorName + ": collection count = "
+ newCollectionCount);
long newCollectionTime = collector.getCollectionTime();
System.out.println(collectorName + ": collection time = "
+ newCollectionTime);
if (newCollectionCount <= collectionCount) {
throw new RuntimeException("No new collection");
}
if (newCollectionTime <= collectionTime) {
throw new RuntimeException("Collector has not run some more");
}
System.out.println("Test passed.");
}
/**
* Utility class to guarantee a mixed GC. The class allocates several arrays and
* promotes them to the oldgen. After that it tries to provoke mixed GC by
* allocating new objects.
*
* The necessary condition for guaranteed mixed GC is running MixedGCProvoker is
* running in VM with the following flags: -XX:MaxTenuringThreshold=1 -Xms12M
* -Xmx12M -XX:G1MixedGCLiveThresholdPercent=100 -XX:G1HeapWastePercent=0
* -XX:G1HeapRegionSize=1m
*/
public class MixedGCProvoker {
private final WhiteBox WB = WhiteBox.getWhiteBox();
private final List<byte[]> liveOldObjects = new ArrayList<>();
private final List<byte[]> newObjects = new ArrayList<>();
public static final int ALLOCATION_SIZE = 20000;
public static final int ALLOCATION_COUNT = 15;
public void allocateOldObjects() {
List<byte[]> deadOldObjects = new ArrayList<>();
// Allocates buffer and promotes it to the old gen. Mix live and dead old
// objects
for (int i = 0; i < ALLOCATION_COUNT; ++i) {
liveOldObjects.add(new byte[ALLOCATION_SIZE * 5]);
deadOldObjects.add(new byte[ALLOCATION_SIZE * 5]);
}
// Do two young collections, MaxTenuringThreshold=1 will force promotion.
// G1HeapRegionSize=1m guarantees that old gen regions will be filled.
WB.youngGC();
WB.youngGC();
// Check it is promoted & keep alive
Asserts.assertTrue(WB.isObjectInOldGen(liveOldObjects),
"List of the objects is suppose to be in OldGen");
Asserts.assertTrue(WB.isObjectInOldGen(deadOldObjects),
"List of the objects is suppose to be in OldGen");
}
/**
* Waits until Concurent Mark Cycle finishes
* @param wb Whitebox instance
* @param sleepTime sleep time
*/
private void waitTillCMCFinished(int sleepTime) {
while (WB.g1InConcurrentMark()) {
if (sleepTime > -1) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
System.out.println("Got InterruptedException while waiting for ConcMarkCycle to finish");
}
}
}
}
public void provokeMixedGC() {
waitTillCMCFinished(0);
WB.g1StartConcMarkCycle();
waitTillCMCFinished(0);
WB.youngGC();
System.out.println("Allocating new objects to provoke mixed GC");
// Provoke a mixed collection. G1MixedGCLiveThresholdPercent=100
// guarantees that full old gen regions will be included.
for (int i = 0; i < (ALLOCATION_COUNT * 20); i++) {
newObjects.add(new byte[ALLOCATION_SIZE]);
}
// check that liveOldObjects still alive
Asserts.assertTrue(WB.isObjectInOldGen(liveOldObjects),
"List of the objects is suppose to be in OldGen");
}
}
}