From 65e9f0b4c719146b0958cb3c01fd31e11e49ec37 Mon Sep 17 00:00:00 2001 Date: Tue, 16 Mar 2021 07:09:57 +0000 Subject: [PATCH 4/4] backport JDK-8214535 to support Jmap parallel --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 25 ++++ src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 4 + .../gc/parallel/parallelScavengeHeap.cpp | 64 +++++++++++ .../gc/parallel/parallelScavengeHeap.hpp | 22 +++- src/hotspot/share/gc/parallel/psOldGen.cpp | 32 ++++++ src/hotspot/share/gc/parallel/psOldGen.hpp | 11 ++ src/hotspot/share/gc/shared/collectedHeap.hpp | 11 ++ .../share/gc/shared/vmGCOperations.cpp | 2 +- .../share/gc/shared/vmGCOperations.hpp | 5 +- src/hotspot/share/gc/shared/workgroup.hpp | 21 ++++ src/hotspot/share/memory/heapInspection.cpp | 108 ++++++++++++++++-- src/hotspot/share/memory/heapInspection.hpp | 44 ++++++- src/hotspot/share/runtime/arguments.hpp | 12 +- src/hotspot/share/services/attachListener.cpp | 15 ++- .../share/classes/sun/tools/jmap/JMap.java | 41 +++++-- test/jdk/sun/tools/jmap/BasicJMapTest.java | 55 +++++++++ 16 files changed, 442 insertions(+), 30 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 7e9c6254c..fd2da14a3 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -77,6 +77,7 @@ #include "gc/shared/weakProcessor.hpp" #include "logging/log.hpp" #include "memory/allocation.hpp" +#include "memory/heapInspection.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceShared.hpp" #include "memory/resourceArea.hpp" @@ -2208,6 +2209,30 @@ void G1CollectedHeap::object_iterate(ObjectClosure* cl) { heap_region_iterate(&blk); } +class G1ParallelObjectIterator : public ParallelObjectIterator { +private: + G1CollectedHeap* _heap; + HeapRegionClaimer _claimer; + +public: + G1ParallelObjectIterator(uint thread_num) : + _heap(G1CollectedHeap::heap()), + _claimer(thread_num == 0 ? G1CollectedHeap::heap()->workers()->active_workers() : thread_num) {} + + virtual void object_iterate(ObjectClosure* cl, uint worker_id) { + _heap->object_iterate_parallel(cl, worker_id, &_claimer); + } +}; + +ParallelObjectIterator* G1CollectedHeap::parallel_object_iterator(uint thread_num) { + return new G1ParallelObjectIterator(thread_num); +} + +void G1CollectedHeap::object_iterate_parallel(ObjectClosure* cl, uint worker_id, HeapRegionClaimer* claimer) { + IterateObjectClosureRegionClosure blk(cl); + heap_region_par_iterate_from_worker_offset(&blk, claimer, worker_id); +} + void G1CollectedHeap::keep_alive(oop obj) { G1BarrierSet::enqueue(obj); } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index bb46cae83..82f59d69b 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1125,9 +1125,13 @@ public: // Iteration functions. + void object_iterate_parallel(ObjectClosure* cl, uint worker_id, HeapRegionClaimer* claimer); + // Iterate over all objects, calling "cl.do_object" on each. virtual void object_iterate(ObjectClosure* cl); + virtual ParallelObjectIterator* parallel_object_iterator(uint thread_num); + virtual void safe_object_iterate(ObjectClosure* cl) { object_iterate(cl); } diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 29f967fb3..66e1b32a6 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -523,6 +523,70 @@ void ParallelScavengeHeap::object_iterate(ObjectClosure* cl) { old_gen()->object_iterate(cl); } +// The HeapBlockClaimer is used during parallel iteration over the heap, +// allowing workers to claim heap areas ("blocks"), gaining exclusive rights to these. +// The eden and survivor spaces are treated as single blocks as it is hard to divide +// these spaces. +// The old space is divided into fixed-size blocks. +class HeapBlockClaimer : public StackObj { + size_t _claimed_index; + +public: + static const size_t InvalidIndex = SIZE_MAX; + static const size_t EdenIndex = 0; + static const size_t SurvivorIndex = 1; + static const size_t NumNonOldGenClaims = 2; + + HeapBlockClaimer() : _claimed_index(EdenIndex) { } + // Claim the block and get the block index. + size_t claim_and_get_block() { + size_t block_index; + block_index = Atomic::add(1u, &_claimed_index) - 1; // TODO: original impl is: Atomic::fetch_and_add(&_claimed_index, 1u); + + PSOldGen* old_gen = ParallelScavengeHeap::heap()->old_gen(); + size_t num_claims = old_gen->num_iterable_blocks() + NumNonOldGenClaims; + + return block_index < num_claims ? block_index : InvalidIndex; + } +}; + +void ParallelScavengeHeap::object_iterate_parallel(ObjectClosure* cl, + HeapBlockClaimer* claimer) { + size_t block_index = claimer->claim_and_get_block(); + // Iterate until all blocks are claimed + if (block_index == HeapBlockClaimer::EdenIndex) { + young_gen()->eden_space()->object_iterate(cl); + block_index = claimer->claim_and_get_block(); + } + if (block_index == HeapBlockClaimer::SurvivorIndex) { + young_gen()->from_space()->object_iterate(cl); + young_gen()->to_space()->object_iterate(cl); + block_index = claimer->claim_and_get_block(); + } + while (block_index != HeapBlockClaimer::InvalidIndex) { + old_gen()->object_iterate_block(cl, block_index - HeapBlockClaimer::NumNonOldGenClaims); + block_index = claimer->claim_and_get_block(); + } +} + +class PSScavengeParallelObjectIterator : public ParallelObjectIterator { +private: + ParallelScavengeHeap* _heap; + HeapBlockClaimer _claimer; + +public: + PSScavengeParallelObjectIterator() : + _heap(ParallelScavengeHeap::heap()), + _claimer() {} + + virtual void object_iterate(ObjectClosure* cl, uint worker_id) { + _heap->object_iterate_parallel(cl, &_claimer); + } +}; + +ParallelObjectIterator* ParallelScavengeHeap::parallel_object_iterator(uint thread_num) { + return new PSScavengeParallelObjectIterator(); +} HeapWord* ParallelScavengeHeap::block_start(const void* addr) const { if (young_gen()->is_in_reserved(addr)) { diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index 5d18efb92..0a9b7bd3f 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -44,6 +44,7 @@ class AdjoiningGenerations; class GCHeapSummary; class GCTaskManager; +class HeapBlockClaimer; class MemoryManager; class MemoryPool; class PSAdaptiveSizePolicy; @@ -79,6 +80,8 @@ class ParallelScavengeHeap : public CollectedHeap { MemoryPool* _survivor_pool; MemoryPool* _old_pool; + WorkGang _workers; + virtual void initialize_serviceability(); void trace_heap(GCWhen::Type when, const GCTracer* tracer); @@ -93,7 +96,20 @@ class ParallelScavengeHeap : public CollectedHeap { public: ParallelScavengeHeap(GenerationSizer* policy) : - CollectedHeap(), _collector_policy(policy), _death_march_count(0) { } + CollectedHeap(), + _collector_policy(policy), + _death_march_count(0), + _young_manager(NULL), + _old_manager(NULL), + _eden_pool(NULL), + _survivor_pool(NULL), + _old_pool(NULL), + _workers("GC Thread", + ParallelGCThreads, + true /* are_GC_task_threads */, + false /* are_ConcurrentGC_threads */) { + _workers.initialize_workers(); + } // For use by VM operations enum CollectionType { @@ -217,6 +233,8 @@ class ParallelScavengeHeap : public CollectedHeap { void object_iterate(ObjectClosure* cl); void safe_object_iterate(ObjectClosure* cl) { object_iterate(cl); } + void object_iterate_parallel(ObjectClosure* cl, HeapBlockClaimer* claimer); + virtual ParallelObjectIterator* parallel_object_iterator(uint thread_num); HeapWord* block_start(const void* addr) const; size_t block_size(const HeapWord* addr) const; @@ -232,6 +250,8 @@ class ParallelScavengeHeap : public CollectedHeap { virtual void gc_threads_do(ThreadClosure* tc) const; virtual void print_tracing_info() const; + virtual WorkGang* get_safepoint_workers() { return &_workers; } + void verify(VerifyOption option /* ignored */); // Resize the young generation. The reserved space for the diff --git a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp index 35844b14b..dbb5148fd 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.cpp +++ b/src/hotspot/share/gc/parallel/psOldGen.cpp @@ -213,6 +213,38 @@ HeapWord* PSOldGen::allocate(size_t word_size) { return res; } +size_t PSOldGen::num_iterable_blocks() const { + return (object_space()->used_in_bytes() + IterateBlockSize - 1) / IterateBlockSize; +} + +void PSOldGen::object_iterate_block(ObjectClosure* cl, size_t block_index) { + size_t block_word_size = IterateBlockSize / HeapWordSize; + assert((block_word_size % (ObjectStartArray::block_size)) == 0, + "Block size not a multiple of start_array block"); + + MutableSpace *space = object_space(); + + HeapWord* begin = space->bottom() + block_index * block_word_size; + HeapWord* end = MIN2(space->top(), begin + block_word_size); + + if (!start_array()->object_starts_in_range(begin, end)) { + return; + } + + // Get object starting at or reaching into this block. + HeapWord* start = start_array()->object_start(begin); + if (start < begin) { + start += oop(start)->size(); + } + assert(start >= begin, + "Object address" PTR_FORMAT " must be larger or equal to block address at " PTR_FORMAT, + p2i(start), p2i(begin)); + // Iterate all objects until the end. + for (HeapWord* p = start; p < end; p += oop(p)->size()) { + cl->do_object(oop(p)); + } +} + HeapWord* PSOldGen::expand_and_allocate(size_t word_size) { expand(word_size*HeapWordSize); if (GCExpandToAllocateDelayMillis > 0) { diff --git a/src/hotspot/share/gc/parallel/psOldGen.hpp b/src/hotspot/share/gc/parallel/psOldGen.hpp index fa27f5a04..fa6e4849b 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.hpp +++ b/src/hotspot/share/gc/parallel/psOldGen.hpp @@ -59,6 +59,9 @@ class PSOldGen : public CHeapObj { const size_t _min_gen_size; const size_t _max_gen_size; + // Block size for parallel iteration + static const size_t IterateBlockSize = 1024 * 1024; + // Used when initializing the _name field. static inline const char* select_name(); @@ -195,6 +198,14 @@ class PSOldGen : public CHeapObj { void oop_iterate(OopIterateClosure* cl) { object_space()->oop_iterate(cl); } void object_iterate(ObjectClosure* cl) { object_space()->object_iterate(cl); } + // Number of blocks to be iterated over in the used part of old gen. + size_t num_iterable_blocks() const; + // Iterate the objects starting in block block_index within [bottom, top) of the + // old gen. The object just reaching into this block is not iterated over. + // A block is an evenly sized non-overlapping part of the old gen of + // IterateBlockSize bytes. + void object_iterate_block(ObjectClosure* cl, size_t block_index); + // Debugging - do not use for time critical operations virtual void print() const; virtual void print_on(outputStream* st) const; diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index 47acf22cb..bcd4da29a 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -28,6 +28,7 @@ #include "gc/shared/gcCause.hpp" #include "gc/shared/gcWhen.hpp" #include "memory/allocation.hpp" +#include "memory/heapInspection.hpp" #include "runtime/handles.hpp" #include "runtime/perfData.hpp" #include "runtime/safepoint.hpp" @@ -42,6 +43,7 @@ // class defines the functions that a heap must implement, and contains // infrastructure common to all heaps. +class AbstractGangTask; class AdaptiveSizePolicy; class BarrierSet; class CollectorPolicy; @@ -83,6 +85,11 @@ class GCHeapLog : public EventLogBase { } }; +class ParallelObjectIterator : public CHeapObj { +public: + virtual void object_iterate(ObjectClosure* cl, uint worker_id) = 0; +}; + // // CollectedHeap // GenCollectedHeap @@ -434,6 +441,10 @@ class CollectedHeap : public CHeapObj { // Iterate over all objects, calling "cl.do_object" on each. virtual void object_iterate(ObjectClosure* cl) = 0; + virtual ParallelObjectIterator* parallel_object_iterator(uint thread_num) { + return NULL; + } + // Similar to object_iterate() except iterates only // over live objects. virtual void safe_object_iterate(ObjectClosure* cl) = 0; diff --git a/src/hotspot/share/gc/shared/vmGCOperations.cpp b/src/hotspot/share/gc/shared/vmGCOperations.cpp index b02305a6e..728290a7b 100644 --- a/src/hotspot/share/gc/shared/vmGCOperations.cpp +++ b/src/hotspot/share/gc/shared/vmGCOperations.cpp @@ -154,7 +154,7 @@ void VM_GC_HeapInspection::doit() { } HeapInspection inspect(_csv_format, _print_help, _print_class_stats, _columns); - inspect.heap_inspection(_out); + inspect.heap_inspection(_out, _parallel_thread_num); } diff --git a/src/hotspot/share/gc/shared/vmGCOperations.hpp b/src/hotspot/share/gc/shared/vmGCOperations.hpp index 65876e559..ef73b45de 100644 --- a/src/hotspot/share/gc/shared/vmGCOperations.hpp +++ b/src/hotspot/share/gc/shared/vmGCOperations.hpp @@ -125,18 +125,21 @@ class VM_GC_HeapInspection: public VM_GC_Operation { private: outputStream* _out; bool _full_gc; + uint _parallel_thread_num; bool _csv_format; // "comma separated values" format for spreadsheet. bool _print_help; bool _print_class_stats; const char* _columns; public: - VM_GC_HeapInspection(outputStream* out, bool request_full_gc) : + VM_GC_HeapInspection(outputStream* out, bool request_full_gc, + uint parallel_thread_num = 1) : VM_GC_Operation(0 /* total collections, dummy, ignored */, GCCause::_heap_inspection /* GC Cause */, 0 /* total full collections, dummy, ignored */, request_full_gc) { _out = out; _full_gc = request_full_gc; + _parallel_thread_num = parallel_thread_num; _csv_format = false; _print_help = false; _print_class_stats = false; diff --git a/src/hotspot/share/gc/shared/workgroup.hpp b/src/hotspot/share/gc/shared/workgroup.hpp index 8b46d3bc4..109649df0 100644 --- a/src/hotspot/share/gc/shared/workgroup.hpp +++ b/src/hotspot/share/gc/shared/workgroup.hpp @@ -228,6 +228,27 @@ protected: virtual AbstractGangWorker* allocate_worker(uint which); }; +// Temporarily try to set the number of active workers. +// It's not guaranteed that it succeeds, and users need to +// query the number of active workers. +class WithUpdatedActiveWorkers : public StackObj { +private: + AbstractWorkGang* const _gang; + const uint _old_active_workers; + +public: + WithUpdatedActiveWorkers(AbstractWorkGang* gang, uint requested_num_workers) : + _gang(gang), + _old_active_workers(gang->active_workers()) { + uint capped_num_workers = MIN2(requested_num_workers, gang->total_workers()); + gang->update_active_workers(capped_num_workers); + } + + ~WithUpdatedActiveWorkers() { + _gang->update_active_workers(_old_active_workers); + } +}; + // Several instances of this class run in parallel as workers for a gang. class AbstractGangWorker: public WorkerThread { public: diff --git a/src/hotspot/share/memory/heapInspection.cpp b/src/hotspot/share/memory/heapInspection.cpp index 9c2cdc117..dbc0eb274 100644 --- a/src/hotspot/share/memory/heapInspection.cpp +++ b/src/hotspot/share/memory/heapInspection.cpp @@ -31,6 +31,7 @@ #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" #include "oops/reflectionAccessorImplKlassHelper.hpp" +#include "runtime/atomic.hpp" #include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -236,6 +237,41 @@ size_t KlassInfoTable::size_of_instances_in_words() const { return _size_of_instances_in_words; } +// Return false if the entry could not be recorded on account +// of running out of space required to create a new entry. +bool KlassInfoTable::merge_entry(const KlassInfoEntry* cie) { + Klass* k = cie->klass(); + KlassInfoEntry* elt = lookup(k); + // elt may be NULL if it's a new klass for which we + // could not allocate space for a new entry in the hashtable. + if (elt != NULL) { + elt->set_count(elt->count() + cie->count()); + elt->set_words(elt->words() + cie->words()); + _size_of_instances_in_words += cie->words(); + return true; + } + return false; +} + +class KlassInfoTableMergeClosure : public KlassInfoClosure { + private: + KlassInfoTable* _dest; + bool _success; + public: + KlassInfoTableMergeClosure(KlassInfoTable* table) : _dest(table), _success(true) {} + void do_cinfo(KlassInfoEntry* cie) { + _success &= _dest->merge_entry(cie); + } + bool success() { return _success; } +}; + +// merge from table +bool KlassInfoTable::merge(KlassInfoTable* table) { + KlassInfoTableMergeClosure closure(this); + table->iterate(&closure); + return closure.success(); +} + int KlassInfoHisto::sort_helper(KlassInfoEntry** e1, KlassInfoEntry** e2) { return (*e1)->compare(*e1,*e2); } @@ -687,7 +723,7 @@ class HistoClosure : public KlassInfoClosure { class RecordInstanceClosure : public ObjectClosure { private: KlassInfoTable* _cit; - size_t _missed_count; + uintx _missed_count; BoolObjectClosure* _filter; public: RecordInstanceClosure(KlassInfoTable* cit, BoolObjectClosure* filter) : @@ -701,7 +737,7 @@ class RecordInstanceClosure : public ObjectClosure { } } - size_t missed_count() { return _missed_count; } + uintx missed_count() { return _missed_count; } private: bool should_visit(oop obj) { @@ -709,15 +745,73 @@ class RecordInstanceClosure : public ObjectClosure { } }; -size_t HeapInspection::populate_table(KlassInfoTable* cit, BoolObjectClosure *filter) { - ResourceMark rm; +// Heap inspection for every worker. +// When native OOM hanppens for KlassInfoTable, set _success to false. +void ParHeapInspectTask::work(uint worker_id) { + uintx missed_count = 0; + bool merge_success = true; + if (!Atomic::load(&_success)) { + // other worker has failed on parallel iteration. + return; + } + KlassInfoTable cit(false); + if (cit.allocation_failed()) { + // fail to allocate memory, stop parallel mode + Atomic::store(false, &_success); + return; + } + RecordInstanceClosure ric(&cit, _filter); + _poi->object_iterate(&ric, worker_id); + missed_count = ric.missed_count(); + { + MutexLocker x(&_mutex); + merge_success = _shared_cit->merge(&cit); + } + if (merge_success) { + Atomic::add(missed_count, &_missed_count); + } else { + Atomic::store(false, &_success); + } +} + +size_t HeapInspection::populate_table(KlassInfoTable* cit, BoolObjectClosure *filter, uint parallel_thread_num) { + // Try parallel first. + if (parallel_thread_num > 1) { + ResourceMark rm; + + WorkGang* gang = Universe::heap()->get_safepoint_workers(); + if (gang != NULL) { + // The GC provided a WorkGang to be used during a safepoint. + + // Can't run with more threads than provided by the WorkGang. + WithUpdatedActiveWorkers update_and_restore(gang, parallel_thread_num); + + ParallelObjectIterator* poi = Universe::heap()->parallel_object_iterator(gang->active_workers()); + if (poi != NULL) { + // The GC supports parallel object iteration. + + ParHeapInspectTask task(poi, cit, filter); + // Run task with the active workers. + + gang->run_task(&task); + + delete poi; + if (task.success()) { + return task.missed_count(); + } + } + } + } + + ResourceMark rm; + // If no parallel iteration available, run serially. RecordInstanceClosure ric(cit, filter); Universe::heap()->safe_object_iterate(&ric); return ric.missed_count(); } -void HeapInspection::heap_inspection(outputStream* st) { +void HeapInspection::heap_inspection(outputStream* st, uint parallel_thread_num) { ResourceMark rm; if (_print_help) { @@ -741,9 +835,9 @@ void HeapInspection::heap_inspection(outputStream* st) { KlassInfoTable cit(_print_class_stats); if (!cit.allocation_failed()) { // populate table with object allocation info - size_t missed_count = populate_table(&cit); + uintx missed_count = populate_table(&cit, NULL, parallel_thread_num); if (missed_count != 0) { - st->print_cr("WARNING: Ran out of C-heap; undercounted " SIZE_FORMAT + st->print_cr("WARNING: Ran out of C-heap; undercounted " UINTX_FORMAT " total instances in data below", missed_count); } diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index d8935dc68..026293bf7 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -25,12 +25,15 @@ #ifndef SHARE_VM_MEMORY_HEAPINSPECTION_HPP #define SHARE_VM_MEMORY_HEAPINSPECTION_HPP +#include "gc/shared/workgroup.hpp" #include "memory/allocation.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.hpp" #include "oops/annotations.hpp" #include "utilities/macros.hpp" +class ParallelObjectIterator; + #if INCLUDE_SERVICES @@ -261,6 +264,8 @@ class KlassInfoTable: public StackObj { void iterate(KlassInfoClosure* cic); bool allocation_failed() { return _buckets == NULL; } size_t size_of_instances_in_words() const; + bool merge(KlassInfoTable* table); + bool merge_entry(const KlassInfoEntry* cie); friend class KlassInfoHisto; friend class KlassHierarchy; @@ -364,11 +369,46 @@ class HeapInspection : public StackObj { bool print_class_stats, const char *columns) : _csv_format(csv_format), _print_help(print_help), _print_class_stats(print_class_stats), _columns(columns) {} - void heap_inspection(outputStream* st) NOT_SERVICES_RETURN; - size_t populate_table(KlassInfoTable* cit, BoolObjectClosure* filter = NULL) NOT_SERVICES_RETURN_(0); + void heap_inspection(outputStream* st, uint parallel_thread_num = 1) NOT_SERVICES_RETURN; + size_t populate_table(KlassInfoTable* cit, BoolObjectClosure* filter = NULL, uint parallel_thread_num = 1) NOT_SERVICES_RETURN_(0); static void find_instances_at_safepoint(Klass* k, GrowableArray* result) NOT_SERVICES_RETURN; private: void iterate_over_heap(KlassInfoTable* cit, BoolObjectClosure* filter = NULL); }; +// Parallel heap inspection task. Parallel inspection can fail due to +// a native OOM when allocating memory for TL-KlassInfoTable. +// _success will be set false on an OOM, and serial inspection tried. +class ParHeapInspectTask : public AbstractGangTask { +private: + ParallelObjectIterator *_poi; + KlassInfoTable *_shared_cit; + BoolObjectClosure *_filter; + uintx _missed_count; + bool _success; + Mutex _mutex; + +public: + ParHeapInspectTask(ParallelObjectIterator *poi, + KlassInfoTable *shared_cit, + BoolObjectClosure *filter) : + AbstractGangTask("Iterating heap"), + _poi(poi), + _shared_cit(shared_cit), + _filter(filter), + _missed_count(0), + _success(true), + _mutex(Mutex::leaf, "Parallel heap iteration data merge lock") {} + + uintx missed_count() const { + return _missed_count; + } + + bool success() { + return _success; + } + + virtual void work(uint worker_id); +}; + #endif // SHARE_VM_MEMORY_HEAPINSPECTION_HPP diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index bd439aab0..9827a4c66 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -450,12 +450,6 @@ class Arguments : AllStatic { static ArgsRange check_memory_size(julong size, julong min_size, julong max_size); static ArgsRange parse_memory_size(const char* s, julong* long_arg, julong min_size, julong max_size = max_uintx); - // Parse a string for a unsigned integer. Returns true if value - // is an unsigned integer greater than or equal to the minimum - // parameter passed and returns the value in uintx_arg. Returns - // false otherwise, with uintx_arg undefined. - static bool parse_uintx(const char* value, uintx* uintx_arg, - uintx min_size); // methods to build strings from individual args static void build_jvm_args(const char* arg); @@ -493,6 +487,12 @@ class Arguments : AllStatic { public: // Parses the arguments, first phase static jint parse(const JavaVMInitArgs* args); + // Parse a string for a unsigned integer. Returns true if value + // is an unsigned integer greater than or equal to the minimum + // parameter passed and returns the value in uintx_arg. Returns + // false otherwise, with uintx_arg undefined. + static bool parse_uintx(const char* value, uintx* uintx_arg, + uintx min_size); // Apply ergonomics static jint apply_ergo(); // Adjusts the arguments after the OS have adjusted the arguments diff --git a/src/hotspot/share/services/attachListener.cpp b/src/hotspot/share/services/attachListener.cpp index fc77970a0..b0f3b2e87 100644 --- a/src/hotspot/share/services/attachListener.cpp +++ b/src/hotspot/share/services/attachListener.cpp @@ -258,9 +258,11 @@ jint dump_heap(AttachOperation* op, outputStream* out) { // // Input arguments :- // arg0: "-live" or "-all" +// arg1: parallel thread number static jint heap_inspection(AttachOperation* op, outputStream* out) { bool live_objects_only = true; // default is true to retain the behavior before this change is made const char* arg0 = op->arg(0); + uint parallel_thread_num = MAX2(1, (uint)os::initial_active_processor_count() * 3 / 8); if (arg0 != NULL && (strlen(arg0) > 0)) { if (strcmp(arg0, "-all") != 0 && strcmp(arg0, "-live") != 0) { out->print_cr("Invalid argument to inspectheap operation: %s", arg0); @@ -268,7 +270,18 @@ static jint heap_inspection(AttachOperation* op, outputStream* out) { } live_objects_only = strcmp(arg0, "-live") == 0; } - VM_GC_HeapInspection heapop(out, live_objects_only /* request full gc */); + + const char* num_str = op->arg(1); + if (num_str != NULL && num_str[0] != '\0') { + uintx num; + if (!Arguments::parse_uintx(num_str, &num, 0)) { + out->print_cr("Invalid parallel thread number: [%s]", num_str); + return JNI_ERR; + } + parallel_thread_num = num == 0 ? parallel_thread_num : (uint)num; + } + + VM_GC_HeapInspection heapop(out, live_objects_only /* request full gc */, parallel_thread_num); VMThread::execute(&heapop); return JNI_OK; } diff --git a/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java b/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java index f2db61ab7..9af74f362 100644 --- a/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java +++ b/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java @@ -149,18 +149,28 @@ public class JMap { throws AttachNotSupportedException, IOException, UnsupportedEncodingException { String liveopt = "-all"; - if (options.equals("") || options.equals("all")) { - // pass - } - else if (options.equals("live")) { - liveopt = "-live"; - } - else { - usage(1); + String parallel = null; + String subopts[] = options.split(","); + + for (int i = 0; i < subopts.length; i++) { + String subopt = subopts[i]; + if (subopt.equals("") || subopt.equals("all")) { + // pass + } else if (subopt.equals("live")) { + liveopt = "-live"; + } else if (subopt.startsWith("parallel=")) { + parallel = subopt.substring("parallel=".length()); + if (parallel == null) { + System.err.println("Fail: no number provided in option: '" + subopt + "'"); + System.exit(1); + } + } else { + usage(1); + } } // inspectHeap is not the same as jcmd GC.class_histogram - executeCommandForPid(pid, "inspectheap", liveopt); + executeCommandForPid(pid, "inspectheap", liveopt, parallel); } private static void dump(String pid, String options) @@ -246,9 +256,8 @@ public class JMap { System.err.println(" to connect to running process and print class loader statistics"); System.err.println(" jmap -finalizerinfo "); System.err.println(" to connect to running process and print information on objects awaiting finalization"); - System.err.println(" jmap -histo[:live] "); + System.err.println(" jmap -histo: "); System.err.println(" to connect to running process and print histogram of java object heap"); - System.err.println(" if the \"live\" suboption is specified, only count live objects"); System.err.println(" jmap -dump: "); System.err.println(" to connect to running process and dump java heap"); System.err.println(" jmap -? -h --help"); @@ -261,6 +270,16 @@ public class JMap { System.err.println(" file= dump heap to "); System.err.println(""); System.err.println(" Example: jmap -dump:live,format=b,file=heap.bin "); + System.err.println(""); + System.err.println(" histo-options:"); + System.err.println(" live count only live objects"); + System.err.println(" all count all objects in the heap (default if one of \"live\" or \"all\" is not specified)"); + System.err.println(" parallel= parallel threads number for heap iteration:"); + System.err.println(" parallel=0 default behavior, use predefined number of threads"); + System.err.println(" parallel=1 disable parallel heap iteration"); + System.err.println(" parallel= use N threads for parallel heap iteration"); + System.err.println(""); + System.err.println(" Example: jmap -histo:live,parallel=2 "); System.exit(exit); } } diff --git a/test/jdk/sun/tools/jmap/BasicJMapTest.java b/test/jdk/sun/tools/jmap/BasicJMapTest.java index c0432dede..960705e24 100644 --- a/test/jdk/sun/tools/jmap/BasicJMapTest.java +++ b/test/jdk/sun/tools/jmap/BasicJMapTest.java @@ -45,6 +45,35 @@ import jdk.testlibrary.ProcessTools; * @build jdk.test.lib.hprof.util.* * @run main/timeout=240 BasicJMapTest */ + +/* + * @test id=Parallel + * @summary Unit test for jmap utility (Parallel GC) + * @key intermittent + * @library /lib/testlibrary + * @library /test/lib + * @build jdk.testlibrary.* + * @build jdk.test.lib.hprof.* + * @build jdk.test.lib.hprof.model.* + * @build jdk.test.lib.hprof.parser.* + * @build jdk.test.lib.hprof.util.* + * @run main/othervm/timeout=240 -XX:+UseParallelGC BasicJMapTest + */ + +/* + * @test id=G1 + * @summary Unit test for jmap utility (G1 GC) + * @key intermittent + * @library /lib/testlibrary + * @library /test/lib + * @build jdk.testlibrary.* + * @build jdk.test.lib.hprof.* + * @build jdk.test.lib.hprof.model.* + * @build jdk.test.lib.hprof.parser.* + * @build jdk.test.lib.hprof.util.* + * @run main/othervm/timeout=240 -XX:+UseG1GC BasicJMapTest + */ + public class BasicJMapTest { private static ProcessBuilder processBuilder = new ProcessBuilder(); @@ -68,6 +97,32 @@ public class BasicJMapTest { output.shouldHaveExitValue(0); } + private static void testHistoParallelZero() throws Exception { + OutputAnalyzer output = jmap("-histo:parallel=0"); + output.shouldHaveExitValue(0); + } + + private static void testHistoParallel() throws Exception { + OutputAnalyzer output = jmap("-histo:parallel=2"); + output.shouldHaveExitValue(0); + } + + private static void testHistoNonParallel() throws Exception { + OutputAnalyzer output = jmap("-histo:parallel=1"); + output.shouldHaveExitValue(0); + } + + private static void testHistoMultipleParameters() throws Exception { + OutputAnalyzer output = jmap("-histo:parallel=2,live"); + output.shouldHaveExitValue(0); + output = jmap("-histo:live,parallel=2"); + output.shouldHaveExitValue(0); + output = jmap("-histo:parallel=2,all"); + output.shouldHaveExitValue(0); + output = jmap("-histo:all,parallel=2"); + output.shouldHaveExitValue(0); + } + private static void testFinalizerInfo() throws Exception { OutputAnalyzer output = jmap("-finalizerinfo"); output.shouldHaveExitValue(0); -- 2.19.0