diff --git a/8214535-support-Jmap-parallel.patch b/8214535-support-Jmap-parallel.patch new file mode 100755 index 0000000000000000000000000000000000000000..7dc84e29cdfb7995c83fc6b727fde8a4b81c2ac4 --- /dev/null +++ b/8214535-support-Jmap-parallel.patch @@ -0,0 +1,858 @@ +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::heap_region_iterate(HeapRegionClosure* cl) const { + _hrm.iterate(cl); + } +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 + diff --git a/java-11-openjdk.spec b/java-11-openjdk.spec index 8556c9b2b6a80de85288e4dc22aba5ddce8f752b..cbcb76d8045303a193cf974a47c8d211c13c7a4b 100644 --- a/java-11-openjdk.spec +++ b/java-11-openjdk.spec @@ -740,7 +740,7 @@ Provides: java-src%{?1} = %{epoch}:%{version}-%{release} Name: java-%{javaver}-%{origin} Version: %{newjavaver}.%{buildver} -Release: 5 +Release: 6 # java-1.5.0-ibm from jpackage.org set Epoch to 1 for unknown reasons # and this change was brought into RHEL-4. java-1.5.0-ibm packages # also included the epoch in their virtual provides. This created a @@ -853,6 +853,7 @@ Patch63: 8217918-C2-XX-AggressiveUnboxing-is-broken.patch Patch64: Fix-the-memcpy-symbol-issue-during-JDK11-x64-build.patch Patch65: add-LazyBox-feature.patch Patch66: add-G1-Full-GC-optimization.patch +Patch67: 8214535-support-Jmap-parallel.patch BuildRequires: autoconf BuildRequires: alsa-lib-devel @@ -1129,6 +1130,7 @@ pushd %{top_level_dir_name} %patch64 -p1 %patch65 -p1 %patch66 -p1 +%patch67 -p1 popd # openjdk %patch1000 @@ -1632,6 +1634,9 @@ require "copy_jdk_configs.lua" %changelog +* Fri Mar 19 2021 aijm - 1:11.0.10.9-6 +- add 8214535-support-Jmap-parallel.patch + * Fri Mar 19 2021 aijm - 1:11.0.10.9-5 - add add-G1-Full-GC-optimization.patch