From 65673b13afd585574ceec40d3c63d03e237cbdb4 Mon Sep 17 00:00:00 2001 From: phh Date: Mon, 28 Jan 2019 17:51:10 +0000 Subject: [PATCH] 8215934: G1 Old Gen MemoryPool CollectionUsage.used values don't reflect mixed GC results Summary: Memory pools can now be optional collection participants, e.g., G1 Old Gen in an incremental collection. Reviewed-by: tschatzl, jcbeyler, andrew --- .../concurrentMarkSweepGeneration.cpp | 5 +- .../gc_implementation/g1/g1CollectedHeap.cpp | 5 +- src/share/vm/services/memoryManager.cpp | 31 ++- src/share/vm/services/memoryManager.hpp | 20 +- src/share/vm/services/memoryService.cpp | 41 ++-- src/share/vm/services/memoryService.hpp | 27 +- .../gc/TestMemoryMXBeansAndPoolsPresence.java | 101 ++++++++ .../g1/mixedgc/TestOldGenCollectionUsage.java | 232 ++++++++++++++++++ 8 files changed, 420 insertions(+), 42 deletions(-) create mode 100644 test/gc/TestMemoryMXBeansAndPoolsPresence.java create mode 100644 test/gc/g1/mixedgc/TestOldGenCollectionUsage.java diff --git a/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp b/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp index f42f1c9e7..1872e72e3 100644 --- a/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp +++ b/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2019, 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 @@ -9541,6 +9541,7 @@ TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorSt case CMSCollector::InitialMarking: initialize(true /* fullGC */ , cause /* cause of the GC */, + true /* allMemoryPoolsAffected */, true /* recordGCBeginTime */, true /* recordPreGCUsage */, false /* recordPeakUsage */, @@ -9553,6 +9554,7 @@ TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorSt case CMSCollector::FinalMarking: initialize(true /* fullGC */ , cause /* cause of the GC */, + true /* allMemoryPoolsAffected */, false /* recordGCBeginTime */, false /* recordPreGCUsage */, false /* recordPeakUsage */, @@ -9565,6 +9567,7 @@ TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorSt case CMSCollector::Sweeping: initialize(true /* fullGC */ , cause /* cause of the GC */, + true /* allMemoryPoolsAffected */, false /* recordGCBeginTime */, false /* recordPreGCUsage */, true /* recordPeakUsage */, diff --git a/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp b/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp index d1ca35e42..e69839c4d 100644 --- a/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp +++ b/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2019, 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 @@ -4008,7 +4008,8 @@ G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) { log_gc_header(); TraceCollectorStats tcs(g1mm()->incremental_collection_counters()); - TraceMemoryManagerStats tms(false /* fullGC */, gc_cause()); + TraceMemoryManagerStats tms(false /* fullGC */, gc_cause(), + yc_type() == Mixed /* allMemoryPoolsAffected */); // If the secondary_free_list is not empty, append it to the // free_list. No need to wait for the cleanup operation to finish; diff --git a/src/share/vm/services/memoryManager.cpp b/src/share/vm/services/memoryManager.cpp index d3c41fed9..26d6ed004 100644 --- a/src/share/vm/services/memoryManager.cpp +++ b/src/share/vm/services/memoryManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2019, 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 @@ -49,13 +49,15 @@ MemoryManager::MemoryManager() { (void)const_cast(_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() { @@ -217,6 +219,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()); @@ -266,7 +277,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(); } @@ -304,8 +316,11 @@ void GCMemoryManager::gc_end(bool recordPostGCUsage, 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); + } } } diff --git a/src/share/vm/services/memoryManager.hpp b/src/share/vm/services/memoryManager.hpp index 8e61f521b..dde6638c7 100644 --- a/src/share/vm/services/memoryManager.hpp +++ b/src/share/vm/services/memoryManager.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2019, 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 @@ -41,11 +41,12 @@ class GCMemoryManager; class OopClosure; class MemoryManager : public CHeapObj { -private: +protected: enum { max_num_pools = 10 }; +private: MemoryPool* _pools[max_num_pools]; int _num_pools; @@ -75,7 +76,7 @@ public: return _pools[index]; } - void add_pool(MemoryPool* pool); + int add_pool(MemoryPool* pool); bool is_manager(instanceHandle mh) { return mh() == _memory_mgr_obj; } @@ -177,10 +178,20 @@ private: GCStatInfo* _current_gc_stat; int _num_gc_threads; volatile bool _notification_enabled; + bool _pool_always_affected_by_gc[MemoryManager::max_num_pools]; + public: GCMemoryManager(); ~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; } @@ -192,7 +203,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(); } diff --git a/src/share/vm/services/memoryService.cpp b/src/share/vm/services/memoryService.cpp index 8115a3e24..31cf2badc 100644 --- a/src/share/vm/services/memoryService.cpp +++ b/src/share/vm/services/memoryService.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2019, 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 @@ -187,7 +187,7 @@ void MemoryService::add_g1_heap_info(G1CollectedHeap* g1h) { _managers_list->append(_major_gc_manager); add_g1YoungGen_memory_pool(g1h, _major_gc_manager, _minor_gc_manager); - add_g1OldGen_memory_pool(g1h, _major_gc_manager); + add_g1OldGen_memory_pool(g1h, _major_gc_manager, _minor_gc_manager); } #endif // INCLUDE_ALL_GCS @@ -241,8 +241,8 @@ MemoryPool* MemoryService::add_cms_space(CompactibleFreeListSpace* space, // Add memory pool(s) for one generation void MemoryService::add_generation_memory_pool(Generation* gen, - MemoryManager* major_mgr, - MemoryManager* minor_mgr) { + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr) { guarantee(gen != NULL, "No generation for memory pool"); Generation::Name kind = gen->kind(); int index = _pools_list->length(); @@ -332,7 +332,9 @@ void MemoryService::add_generation_memory_pool(Generation* gen, #if INCLUDE_ALL_GCS -void MemoryService::add_psYoung_memory_pool(PSYoungGen* gen, MemoryManager* major_mgr, MemoryManager* minor_mgr) { +void MemoryService::add_psYoung_memory_pool(PSYoungGen* gen, + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr) { assert(major_mgr != NULL && minor_mgr != NULL, "Should have two managers"); // Add a memory pool for each space and young gen doesn't @@ -356,7 +358,7 @@ void MemoryService::add_psYoung_memory_pool(PSYoungGen* gen, MemoryManager* majo _pools_list->append(survivor); } -void MemoryService::add_psOld_memory_pool(PSOldGen* gen, MemoryManager* mgr) { +void MemoryService::add_psOld_memory_pool(PSOldGen* gen, GCMemoryManager* mgr) { PSGenerationPool* old_gen = new PSGenerationPool(gen, "PS Old Gen", MemoryPool::Heap, @@ -366,8 +368,8 @@ void MemoryService::add_psOld_memory_pool(PSOldGen* gen, MemoryManager* mgr) { } void MemoryService::add_g1YoungGen_memory_pool(G1CollectedHeap* g1h, - MemoryManager* major_mgr, - MemoryManager* minor_mgr) { + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr) { assert(major_mgr != NULL && minor_mgr != NULL, "should have two managers"); G1EdenPool* eden = new G1EdenPool(g1h); @@ -382,11 +384,13 @@ void MemoryService::add_g1YoungGen_memory_pool(G1CollectedHeap* g1h, } void MemoryService::add_g1OldGen_memory_pool(G1CollectedHeap* g1h, - MemoryManager* mgr) { - assert(mgr != NULL, "should have one manager"); + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr) { + assert(major_mgr != NULL && minor_mgr != NULL, "should have two managers"); G1OldGenPool* old_gen = new G1OldGenPool(g1h); - mgr->add_pool(old_gen); + major_mgr->add_pool(old_gen); + minor_mgr->add_pool(old_gen, false /* always_affected_by_gc */); _pools_list->append(old_gen); } #endif // INCLUDE_ALL_GCS @@ -484,7 +488,8 @@ void MemoryService::gc_begin(bool fullGC, bool recordGCBeginTime, void MemoryService::gc_end(bool fullGC, bool recordPostGCUsage, bool recordAccumulatedGCTime, bool recordGCEndTime, bool countCollection, - GCCause::Cause cause) { + GCCause::Cause cause, + bool allMemoryPoolsAffected) { GCMemoryManager* mgr; if (fullGC) { @@ -496,7 +501,7 @@ void MemoryService::gc_end(bool fullGC, bool recordPostGCUsage, // register the GC end statistics and memory usage mgr->gc_end(recordPostGCUsage, recordAccumulatedGCTime, recordGCEndTime, - countCollection, cause); + countCollection, cause, allMemoryPoolsAffected); } void MemoryService::oops_do(OopClosure* f) { @@ -573,10 +578,11 @@ TraceMemoryManagerStats::TraceMemoryManagerStats(Generation::Name kind, GCCause: } // this has to be called in a stop the world pause and represent // an entire gc pause, start to finish: - initialize(_fullGC, cause,true, true, true, true, true, true, true); + initialize(_fullGC, cause, true, true, true, true, true, true, true, true); } TraceMemoryManagerStats::TraceMemoryManagerStats(bool fullGC, GCCause::Cause cause, + bool allMemoryPoolsAffected, bool recordGCBeginTime, bool recordPreGCUsage, bool recordPeakUsage, @@ -584,7 +590,8 @@ TraceMemoryManagerStats::TraceMemoryManagerStats(bool fullGC, bool recordAccumulatedGCTime, bool recordGCEndTime, bool countCollection) { - initialize(fullGC, cause, recordGCBeginTime, recordPreGCUsage, recordPeakUsage, + initialize(fullGC, cause, allMemoryPoolsAffected, + recordGCBeginTime, recordPreGCUsage, recordPeakUsage, recordPostGCUsage, recordAccumulatedGCTime, recordGCEndTime, countCollection); } @@ -593,6 +600,7 @@ TraceMemoryManagerStats::TraceMemoryManagerStats(bool fullGC, // the MemoryService void TraceMemoryManagerStats::initialize(bool fullGC, GCCause::Cause cause, + bool allMemoryPoolsAffected, bool recordGCBeginTime, bool recordPreGCUsage, bool recordPeakUsage, @@ -601,6 +609,7 @@ void TraceMemoryManagerStats::initialize(bool fullGC, bool recordGCEndTime, bool countCollection) { _fullGC = fullGC; + _allMemoryPoolsAffected = allMemoryPoolsAffected; _recordGCBeginTime = recordGCBeginTime; _recordPreGCUsage = recordPreGCUsage; _recordPeakUsage = recordPeakUsage; @@ -616,5 +625,5 @@ void TraceMemoryManagerStats::initialize(bool fullGC, TraceMemoryManagerStats::~TraceMemoryManagerStats() { MemoryService::gc_end(_fullGC, _recordPostGCUsage, _recordAccumulatedGCTime, - _recordGCEndTime, _countCollection, _cause); + _recordGCEndTime, _countCollection, _cause, _allMemoryPoolsAffected); } diff --git a/src/share/vm/services/memoryService.hpp b/src/share/vm/services/memoryService.hpp index 23979ed67..ce1685961 100644 --- a/src/share/vm/services/memoryService.hpp +++ b/src/share/vm/services/memoryService.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2019, 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 @@ -77,25 +77,26 @@ private: static MemoryPool* _compressed_class_pool; static void add_generation_memory_pool(Generation* gen, - MemoryManager* major_mgr, - MemoryManager* minor_mgr); + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr); static void add_generation_memory_pool(Generation* gen, - MemoryManager* major_mgr) { + GCMemoryManager* major_mgr) { add_generation_memory_pool(gen, major_mgr, NULL); } static void add_psYoung_memory_pool(PSYoungGen* gen, - MemoryManager* major_mgr, - MemoryManager* minor_mgr); + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr); static void add_psOld_memory_pool(PSOldGen* gen, - MemoryManager* mgr); + GCMemoryManager* mgr); static void add_g1YoungGen_memory_pool(G1CollectedHeap* g1h, - MemoryManager* major_mgr, - MemoryManager* minor_mgr); + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr); static void add_g1OldGen_memory_pool(G1CollectedHeap* g1h, - MemoryManager* mgr); + GCMemoryManager* major_mgr, + GCMemoryManager* minor_mgr); static MemoryPool* add_space(ContiguousSpace* space, const char* name, @@ -162,7 +163,8 @@ public: static void gc_end(bool fullGC, bool recordPostGCUsage, bool recordAccumulatedGCTime, bool recordGCEndTime, bool countCollection, - GCCause::Cause cause); + GCCause::Cause cause, + bool allMemoryPoolsAffected); static void oops_do(OopClosure* f); @@ -185,6 +187,7 @@ public: class TraceMemoryManagerStats : public StackObj { private: bool _fullGC; + bool _allMemoryPoolsAffected; bool _recordGCBeginTime; bool _recordPreGCUsage; bool _recordPeakUsage; @@ -197,6 +200,7 @@ public: TraceMemoryManagerStats() {} TraceMemoryManagerStats(bool fullGC, GCCause::Cause cause, + bool allMemoryPoolsAffected = true, bool recordGCBeginTime = true, bool recordPreGCUsage = true, bool recordPeakUsage = true, @@ -207,6 +211,7 @@ public: void initialize(bool fullGC, GCCause::Cause cause, + bool allMemoryPoolsAffected, bool recordGCBeginTime, bool recordPreGCUsage, bool recordPeakUsage, diff --git a/test/gc/TestMemoryMXBeansAndPoolsPresence.java b/test/gc/TestMemoryMXBeansAndPoolsPresence.java new file mode 100644 index 000000000..85ce5cd1d --- /dev/null +++ b/test/gc/TestMemoryMXBeansAndPoolsPresence.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017, 2019, 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 TestMemoryMXBeansAndPoolsPresence + * @key gc + * @bug 8191564 + * @summary Tests that GarbageCollectorMXBeans and GC MemoryPools are created. + * @library /testlibrary + * @requires vm.gc == null + * @run main/othervm -XX:+UseParallelGC TestMemoryMXBeansAndPoolsPresence Parallel + * @run main/othervm -XX:+UseSerialGC TestMemoryMXBeansAndPoolsPresence Serial + * @run main/othervm -XX:+UseConcMarkSweepGC TestMemoryMXBeansAndPoolsPresence CMS + * @run main/othervm -XX:+UseG1GC TestMemoryMXBeansAndPoolsPresence G1 + */ + +import java.util.List; +import java.util.ArrayList; +import java.lang.management.*; +import java.util.stream.*; + +import com.oracle.java.testlibrary.Asserts; + +class GCBeanDescription { + public String name; + public String[] poolNames; + + public GCBeanDescription(String name, String[] poolNames) { + this.name = name; + this.poolNames = poolNames; + } +} + +public class TestMemoryMXBeansAndPoolsPresence { + public static void test(GCBeanDescription... expectedBeans) { + List memoryPools = ManagementFactory.getMemoryPoolMXBeans(); + + List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); + Asserts.assertEQ(expectedBeans.length, gcBeans.size()); + + for (GCBeanDescription desc : expectedBeans) { + List beans = gcBeans.stream() + .filter(b -> b.getName().equals(desc.name)) + .collect(Collectors.toList()); + Asserts.assertEQ(beans.size(), 1); + + GarbageCollectorMXBean bean = beans.get(0); + Asserts.assertEQ(desc.name, bean.getName()); + + String[] pools = bean.getMemoryPoolNames(); + Asserts.assertEQ(desc.poolNames.length, pools.length); + for (int i = 0; i < desc.poolNames.length; i++) { + Asserts.assertEQ(desc.poolNames[i], pools[i]); + } + } + } + + 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", "G1 Old Gen"}), + new GCBeanDescription("G1 Old Generation", new String[] {"G1 Eden Space", "G1 Survivor Space", "G1 Old Gen"})); + break; + case "CMS": + test(new GCBeanDescription("ParNew", new String[] {"Par Eden Space", "Par Survivor Space"}), + new GCBeanDescription("ConcurrentMarkSweep", new String[] {"Par Eden Space", "Par Survivor Space", "CMS Old Gen"})); + break; + case "Parallel": + test(new GCBeanDescription("PS Scavenge", new String[] {"PS Eden Space", "PS Survivor Space"}), + new GCBeanDescription("PS MarkSweep", new String[] {"PS Eden Space", "PS Survivor Space", "PS Old Gen"})); + break; + case "Serial": + test(new GCBeanDescription("Copy", new String[] {"Eden Space", "Survivor Space"}), + new GCBeanDescription("MarkSweepCompact", new String[] {"Eden Space", "Survivor Space", "Tenured Gen"})); + break; + default: + Asserts.assertTrue(false); + break; + + } + } +} diff --git a/test/gc/g1/mixedgc/TestOldGenCollectionUsage.java b/test/gc/g1/mixedgc/TestOldGenCollectionUsage.java new file mode 100644 index 000000000..bd032e347 --- /dev/null +++ b/test/gc/g1/mixedgc/TestOldGenCollectionUsage.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2018, 2019, 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" | vm.gc=="null" + * @requires vm.opt.MaxGCPauseMillis == "null" + * @library /testlibrary /testlibrary/whitebox + * @build ClassFileInstaller com.oracle.java.testlibrary.* sun.hotspot.WhiteBox TestOldGenCollectionUsage + * @run main ClassFileInstaller sun.hotspot.WhiteBox + * sun.hotspot.WhiteBox$WhiteBoxPermission + * @run main/othervm -Xbootclasspath/a:. -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -verbose:gc -XX:SurvivorRatio=1 -Xmx14m -Xms14m -XX:MaxTenuringThreshold=1 + * -XX:InitiatingHeapOccupancyPercent=100 -XX:G1MixedGCCountTarget=4 -XX:MaxGCPauseMillis=30000 + * -XX:G1HeapRegionSize=1m -XX:G1HeapWastePercent=0 -XX:G1MixedGCLiveThresholdPercent=100 + * TestOldGenCollectionUsage + */ + +import com.oracle.java.testlibrary.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 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 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 liveOldObjects = new ArrayList<>(); + private final List newObjects = new ArrayList<>(); + + public static final int ALLOCATION_SIZE = 20000; + public static final int ALLOCATION_COUNT = 15; + + public void allocateOldObjects() { + List 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++) { + try { + newObjects.add(new byte[ALLOCATION_SIZE]); + } catch (OutOfMemoryError e) { + newObjects.clear(); + WB.youngGC(); + WB.youngGC(); + System.out.println("OutOfMemoryError is reported, stop allocating new objects"); + break; + } + } + // check that liveOldObjects still alive + Asserts.assertTrue(WB.isObjectInOldGen(liveOldObjects), + "List of the objects is suppose to be in OldGen"); + } + + } + +} -- GitLab