diff --git a/src/hotspot/share/gc/shared/gcTrace.cpp b/src/hotspot/share/gc/shared/gcTrace.cpp index bad9c707b1e51..a8d60d7d1799c 100644 --- a/src/hotspot/share/gc/shared/gcTrace.cpp +++ b/src/hotspot/share/gc/shared/gcTrace.cpp @@ -27,7 +27,6 @@ #include "gc/shared/gcId.hpp" #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTrace.hpp" -#include "gc/shared/objectCountEventSender.hpp" #include "gc/shared/referenceProcessorStats.hpp" #include "memory/heapInspection.hpp" #include "memory/resourceArea.hpp" @@ -74,31 +73,6 @@ void GCTracer::report_gc_reference_stats(const ReferenceProcessorStats& rps) con } #if INCLUDE_SERVICES -class ObjectCountEventSenderClosure : public KlassInfoClosure { - const double _size_threshold_percentage; - const size_t _total_size_in_words; - const Ticks _timestamp; - - public: - ObjectCountEventSenderClosure(size_t total_size_in_words, const Ticks& timestamp) : - _size_threshold_percentage(ObjectCountCutOffPercent / 100), - _total_size_in_words(total_size_in_words), - _timestamp(timestamp) - {} - - virtual void do_cinfo(KlassInfoEntry* entry) { - if (should_send_event(entry)) { - ObjectCountEventSender::send(entry, _timestamp); - } - } - - private: - bool should_send_event(const KlassInfoEntry* entry) const { - double percentage_of_heap = ((double) entry->words()) / _total_size_in_words; - return percentage_of_heap >= _size_threshold_percentage; - } -}; - void GCTracer::report_object_count_after_gc(BoolObjectClosure* is_alive_cl, WorkerThreads* workers) { assert(is_alive_cl != nullptr, "Must supply function to check liveness"); diff --git a/src/hotspot/share/gc/shared/gcTrace.hpp b/src/hotspot/share/gc/shared/gcTrace.hpp index 6a47e54090f88..c137123535b11 100644 --- a/src/hotspot/share/gc/shared/gcTrace.hpp +++ b/src/hotspot/share/gc/shared/gcTrace.hpp @@ -30,7 +30,9 @@ #include "gc/shared/gcId.hpp" #include "gc/shared/gcName.hpp" #include "gc/shared/gcWhen.hpp" +#include "gc/shared/objectCountEventSender.hpp" #include "gc/shared/workerThread.hpp" +#include "memory/heapInspection.hpp" #include "memory/metaspace.hpp" #include "memory/referenceType.hpp" #include "utilities/macros.hpp" @@ -92,6 +94,33 @@ class ParallelOldGCInfo { void* dense_prefix() const { return _dense_prefix; } }; +#if INCLUDE_SERVICES +class ObjectCountEventSenderClosure : public KlassInfoClosure { + const double _size_threshold_percentage; + const size_t _total_size_in_words; + const Ticks _timestamp; + + public: + ObjectCountEventSenderClosure(size_t total_size_in_words, const Ticks& timestamp) : + _size_threshold_percentage(ObjectCountCutOffPercent / 100), + _total_size_in_words(total_size_in_words), + _timestamp(timestamp) + {} + + virtual void do_cinfo(KlassInfoEntry* entry) { + if (should_send_event(entry)) { + ObjectCountEventSender::send(entry, _timestamp); + } + } + + private: + bool should_send_event(const KlassInfoEntry* entry) const { + double percentage_of_heap = ((double) entry->words()) / _total_size_in_words; + return percentage_of_heap >= _size_threshold_percentage; + } +}; +#endif // INCLUDE_SERVICES + class GCTracer { protected: SharedGCInfo _shared_gc_info; @@ -104,6 +133,10 @@ class GCTracer { void report_metaspace_summary(GCWhen::Type when, const MetaspaceSummary& metaspace_summary) const; void report_gc_reference_stats(const ReferenceProcessorStats& rp) const; void report_object_count_after_gc(BoolObjectClosure* object_filter, WorkerThreads* workers) NOT_SERVICES_RETURN; + // Report object count without performing a heap inspection. This method will + // only work if there's a global KlassInfoTable in the heap. + template + void report_object_count() NOT_SERVICES_RETURN; void report_cpu_time_event(double user_time, double system_time, double real_time) const; protected: diff --git a/src/hotspot/share/gc/shared/gcTrace.inline.hpp b/src/hotspot/share/gc/shared/gcTrace.inline.hpp new file mode 100644 index 0000000000000..fa77bc55bc0d3 --- /dev/null +++ b/src/hotspot/share/gc/shared/gcTrace.inline.hpp @@ -0,0 +1,25 @@ +#ifndef SHARE_GC_SHARED_GCTRACE_INLINE_HPP +#define SHARE_GC_SHARED_GCTRACE_INLINE_HPP + +#include "gc/shared/gcTrace.hpp" +#include "memory/heapInspection.hpp" +#include "utilities/macros.hpp" + +#if INCLUDE_SERVICES +template +void GCTracer::report_object_count() { + if (!ObjectCountEventSender::should_send_event()) { + return; + } + + T* heap = T::heap(); + KlassInfoTable* cit = heap->get_cit(); + + if (!cit->allocation_failed()) { + ObjectCountEventSenderClosure event_sender(cit->size_of_instances_in_words(), Ticks::now()); + cit->iterate(&event_sender); + } +} +#endif // INCLUDE_SERVICES + +#endif // SHARE_GC_SHARED_GCTRACE_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/objectCountEventSender.cpp b/src/hotspot/share/gc/shared/objectCountEventSender.cpp index a28c52e5c901d..412428dc682b0 100644 --- a/src/hotspot/share/gc/shared/objectCountEventSender.cpp +++ b/src/hotspot/share/gc/shared/objectCountEventSender.cpp @@ -22,7 +22,6 @@ * */ - #include "gc/shared/gcId.hpp" #include "gc/shared/objectCountEventSender.hpp" #include "jfr/jfrEvents.hpp" diff --git a/src/hotspot/share/gc/shared/objectCountEventSender.hpp b/src/hotspot/share/gc/shared/objectCountEventSender.hpp index 115fbfdaf7d3a..25aeff5be5bda 100644 --- a/src/hotspot/share/gc/shared/objectCountEventSender.hpp +++ b/src/hotspot/share/gc/shared/objectCountEventSender.hpp @@ -25,8 +25,8 @@ #ifndef SHARE_GC_SHARED_OBJECTCOUNTEVENTSENDER_HPP #define SHARE_GC_SHARED_OBJECTCOUNTEVENTSENDER_HPP -#include "gc/shared/gcTrace.hpp" #include "memory/allStatic.hpp" +#include "memory/heapInspection.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/ticks.hpp" diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp index 0c223ee3128be..a161b365d7a53 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp @@ -27,6 +27,7 @@ #include "code/nmethod.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "gc/shenandoah/shenandoahGenerationType.hpp" +#include "gc/shenandoah/shenandoahObjectCountClosure.hpp" #include "gc/shenandoah/shenandoahTaskqueue.hpp" #include "memory/iterator.hpp" #include "runtime/javaThread.hpp" @@ -73,7 +74,8 @@ class ShenandoahMarkRefsSuperClosure : public ShenandoahSuperClosure { protected: template - void work(T *p); + // Return true if object was not previously marked strong by another thread. + bool work(T *p); public: inline ShenandoahMarkRefsSuperClosure(ShenandoahObjToScanQueue* q, ShenandoahReferenceProcessor* rp, ShenandoahObjToScanQueue* old_q); @@ -106,6 +108,29 @@ class ShenandoahMarkRefsClosure : public ShenandoahMarkRefsSuperClosure { virtual void do_oop(oop* p) { do_oop_work(p); } }; +#if INCLUDE_JFR +template +class ShenandoahMarkRefsAndCountClosure : public ShenandoahMarkRefsSuperClosure { +private: + ShenandoahObjectCountClosure* _count; + template + inline void do_oop_work(T* p) { + // Count newly marked strong references to avoid double counting. + const bool newly_marked_strong = work(p); + if (newly_marked_strong) { + _count->do_oop(p); + } + } + +public: + ShenandoahMarkRefsAndCountClosure(ShenandoahObjToScanQueue* q, ShenandoahReferenceProcessor* rp, ShenandoahObjToScanQueue* old_q, ShenandoahObjectCountClosure* count) : + ShenandoahMarkRefsSuperClosure(q, rp, old_q), _count(count) {}; + + virtual void do_oop(narrowOop* p) { do_oop_work(p); } + virtual void do_oop(oop* p) { do_oop_work(p); } +}; +#endif // INCLUDE_JFR + class ShenandoahForwardedIsAliveClosure : public BoolObjectClosure { private: ShenandoahMarkingContext* const _mark_context; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp index 725e4e6e3e9f9..cf642f762b6f8 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp @@ -77,8 +77,8 @@ ShenandoahMarkRefsSuperClosure::ShenandoahMarkRefsSuperClosure(ShenandoahObjToSc _weak(false) {} template -inline void ShenandoahMarkRefsSuperClosure::work(T* p) { - ShenandoahMark::mark_through_ref(p, _queue, _old_queue, _mark_context, _weak); +inline bool ShenandoahMarkRefsSuperClosure::work(T* p) { + return ShenandoahMark::mark_through_ref(p, _queue, _old_queue, _mark_context, _weak); } ShenandoahForwardedIsAliveClosure::ShenandoahForwardedIsAliveClosure() : diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index d4c7ad5df50bf..e9ee01a14a55d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -28,6 +28,7 @@ #include "gc/shared/barrierSetNMethod.hpp" #include "gc/shared/collectorCounters.hpp" #include "gc/shared/continuationGCSupport.inline.hpp" +#include "gc/shared/gcTrace.inline.hpp" #include "gc/shenandoah/shenandoahBreakpoint.hpp" #include "gc/shenandoah/shenandoahClosures.inline.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" @@ -314,6 +315,9 @@ void ShenandoahConcurrentGC::vmop_entry_final_mark() { heap->try_inject_alloc_failure(); VM_ShenandoahFinalMarkStartEvac op(this); VMThread::execute(&op); // jump to entry_final_mark under safepoint + // Do not report object count during a safepoint + assert(!ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Should not be at safepoint"); + heap->tracer()->report_object_count(); } void ShenandoahConcurrentGC::vmop_entry_init_update_refs() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp index facba2236be81..9abea1f1639a3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp @@ -23,7 +23,7 @@ * */ - +#include "gc/shared/objectCountEventSender.hpp" #include "gc/shared/satbMarkQueue.hpp" #include "gc/shared/strongRootsScope.hpp" #include "gc/shared/taskTerminator.hpp" @@ -33,6 +33,7 @@ #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMark.inline.hpp" +#include "gc/shenandoah/shenandoahObjectCountClosure.hpp" #include "gc/shenandoah/shenandoahPhaseTimings.hpp" #include "gc/shenandoah/shenandoahReferenceProcessor.hpp" #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" @@ -45,6 +46,7 @@ #include "runtime/continuation.hpp" #include "runtime/threads.hpp" + template class ShenandoahConcurrentMarkingTask : public WorkerTask { private: @@ -170,8 +172,24 @@ void ShenandoahMarkConcurrentRootsTask::work(uint worker_id) { ShenandoahObjToScanQueue* q = _queue_set->queue(worker_id); ShenandoahObjToScanQueue* old_q = (_old_queue_set == nullptr) ? nullptr : _old_queue_set->queue(worker_id); - ShenandoahMarkRefsClosure cl(q, _rp, old_q); - _root_scanner.roots_do(&cl, worker_id); + +#if INCLUDE_JFR + // Use object counting closure if ObjectCount or ObjectCountAfterGC event is enabled. + const bool object_count_enabled = ObjectCountEventSender::should_send_event(); + if (object_count_enabled && !ShenandoahHeap::heap()->mode()->is_generational()) { + KlassInfoTable* const global_cit = ShenandoahHeap::heap()->get_cit(); + KlassInfoTable local_cit(false); + ShenandoahIsAliveClosure is_alive; + ShenandoahObjectCountClosure count(&local_cit, &is_alive); + ShenandoahMarkRefsAndCountClosure cl(q, _rp, old_q, &count); + _root_scanner.roots_do(&cl, worker_id); + count.merge_table(global_cit); + } else +#endif // INCLUDE_JFR + { + ShenandoahMarkRefsClosure cl(q, _rp, old_q); + _root_scanner.roots_do(&cl, worker_id); + } } void ShenandoahConcurrentMark::mark_concurrent_roots() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 6290101bc49d4..6d1efe7d8e5e2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -125,6 +125,12 @@ void ShenandoahControlThread::run_service() { assert (!gc_requested || cause != GCCause::_last_gc_cause, "GC cause should be set"); if (gc_requested) { + // Create the KlassInfoTable for Shenandoah only if JFR is enabled. +#if INCLUDE_JFR + KlassInfoTable cit(false); + heap->set_cit(&cit); +#endif // INCLUDE_JFR + // Cannot uncommit bitmap slices during concurrent reset ShenandoahNoUncommitMark forbid_region_uncommit(heap); @@ -224,6 +230,9 @@ void ShenandoahControlThread::run_service() { // Print Metaspace change following GC (if logging is enabled). MetaspaceUtils::print_metaspace_change(meta_sizes); +#if INCLUDE_JFR + heap->set_cit(nullptr); +#endif // INCLUDE_JFR } // Check if we have seen a new target for soft max heap size or if a gc was requested. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index b90468501f67f..049b51a1d1f64 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -533,6 +533,7 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _active_generation(nullptr), _initial_size(0), _committed(0), + _cit(nullptr), _max_workers(MAX3(ConcGCThreads, ParallelGCThreads, 1U)), _workers(nullptr), _safepoint_workers(nullptr), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index bec1235e94140..2fb1e74df024b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -43,6 +43,7 @@ #include "gc/shenandoah/shenandoahPadding.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "gc/shenandoah/shenandoahUnload.hpp" +#include "memory/heapInspection.hpp" #include "memory/metaspace.hpp" #include "services/memoryManager.hpp" #include "utilities/globalDefinitions.hpp" @@ -228,7 +229,8 @@ class ShenandoahHeap : public CollectedHeap { shenandoah_padding(0); volatile size_t _committed; shenandoah_padding(1); - + // Used for JFR object count event support. + KlassInfoTable* _cit; void increase_used(const ShenandoahAllocRequest& req); public: @@ -252,6 +254,10 @@ class ShenandoahHeap : public CollectedHeap { void set_soft_max_capacity(size_t v); + // Setter & accessor for class histogram + inline void set_cit(KlassInfoTable* cit); + inline KlassInfoTable* get_cit(); + // ---------- Periodic Tasks // public: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp index cf9d808f7ce8f..7eb5bf8a195e6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp @@ -46,6 +46,7 @@ #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahThreadLocalData.hpp" #include "gc/shenandoah/shenandoahWorkGroup.hpp" +#include "memory/heapInspection.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" @@ -647,4 +648,15 @@ inline ShenandoahMarkingContext* ShenandoahHeap::marking_context() const { return _marking_context; } +inline void ShenandoahHeap::set_cit(KlassInfoTable* cit) { + assert(_cit == nullptr || cit == nullptr, "Overwriting an existing histogram"); + assert(_cit != nullptr || cit != nullptr, "Already cleared"); + _cit = cit; +} + +inline KlassInfoTable* ShenandoahHeap::get_cit() { + assert(_cit != nullptr, "KlassInfoTable is null"); + return _cit; +} + #endif // SHARE_GC_SHENANDOAH_SHENANDOAHHEAP_INLINE_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index 2a4149ee44dc4..458848cfea793 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -24,7 +24,7 @@ */ - +#include "gc/shared/objectCountEventSender.hpp" #include "gc/shenandoah/shenandoahBarrierSet.hpp" #include "gc/shenandoah/shenandoahClosures.inline.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" @@ -69,9 +69,25 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, ShenandoahRefe Closure cl(q, rp, old_q); mark_loop_work(&cl, ld, w, t, req); } else { - using Closure = ShenandoahMarkRefsClosure; - Closure cl(q, rp, old_q); - mark_loop_work(&cl, ld, w, t, req); +#if INCLUDE_JFR + // Use object counting closure if ObjectCount or ObjectCountAfterGC event is enabled. + const bool object_count_enabled = ObjectCountEventSender::should_send_event(); + if (object_count_enabled && !ShenandoahHeap::heap()->mode()->is_generational()) { + KlassInfoTable* const global_cit = ShenandoahHeap::heap()->get_cit(); + KlassInfoTable local_cit(false); + ShenandoahIsAliveClosure is_alive; + ShenandoahObjectCountClosure count(&local_cit, &is_alive); + using Closure = ShenandoahMarkRefsAndCountClosure; + Closure cl(q, rp, old_q, &count); + mark_loop_work(&cl, ld, w, t, req); + count.merge_table(global_cit); + } else +#endif // INCLUDE_JFR + { + using Closure = ShenandoahMarkRefsClosure; + Closure cl(q, rp, old_q); + mark_loop_work(&cl, ld, w, t, req); + } } heap->flush_liveness_cache(w); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp index 4aef14f2c9aba..d8526ac8f2f62 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp @@ -56,8 +56,9 @@ class ShenandoahMark: public StackObj { ShenandoahMark(ShenandoahGeneration* generation); public: + // Returns true if object was strongly marked. template - static inline void mark_through_ref(T* p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak); + static inline bool mark_through_ref(T* p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak); // Loom support void start_mark(); @@ -98,10 +99,12 @@ class ShenandoahMark: public StackObj { template static bool in_generation(ShenandoahHeap* const heap, oop obj); + // Returns true if object was strongly marked. template - static void mark_non_generational_ref(T *p, ShenandoahObjToScanQueue* q, ShenandoahMarkingContext* const mark_context, bool weak); + static bool mark_non_generational_ref(T *p, ShenandoahObjToScanQueue* q, ShenandoahMarkingContext* const mark_context, bool weak); - static void mark_ref(ShenandoahObjToScanQueue* q, + // Returns true if object was strongly marked. + static bool mark_ref(ShenandoahObjToScanQueue* q, ShenandoahMarkingContext* const mark_context, bool weak, oop obj); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 2dc0813e51354..eb7aabe393641 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -293,10 +293,11 @@ bool ShenandoahMark::in_generation(ShenandoahHeap* const heap, oop obj) { } template -inline void ShenandoahMark::mark_through_ref(T *p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak) { +inline bool ShenandoahMark::mark_through_ref(T *p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak) { // Note: This is a very hot code path, so the code should be conditional on GENERATION template // parameter where possible, in order to generate the most efficient code. + bool strongly_marked = false; T o = RawAccess<>::oop_load(p); if (!CompressedOops::is_null(o)) { oop obj = CompressedOops::decode_not_null(o); @@ -305,7 +306,7 @@ inline void ShenandoahMark::mark_through_ref(T *p, ShenandoahObjToScanQueue* q, shenandoah_assert_not_forwarded(p, obj); shenandoah_assert_not_in_cset_except(p, obj, heap->cancelled_gc()); if (in_generation(heap, obj)) { - mark_ref(q, mark_context, weak, obj); + strongly_marked = mark_ref(q, mark_context, weak, obj); shenandoah_assert_marked(p, obj); if (GENERATION == YOUNG && heap->is_in_old(p)) { // Mark card as dirty because remembered set scanning still finds interesting pointer. @@ -316,7 +317,7 @@ inline void ShenandoahMark::mark_through_ref(T *p, ShenandoahObjToScanQueue* q, } } else if (old_q != nullptr) { // Young mark, bootstrapping old_q or concurrent with old_q marking. - mark_ref(old_q, mark_context, weak, obj); + strongly_marked = mark_ref(old_q, mark_context, weak, obj); shenandoah_assert_marked(p, obj); } else if (GENERATION == OLD) { // Old mark, found a young pointer. @@ -326,48 +327,54 @@ inline void ShenandoahMark::mark_through_ref(T *p, ShenandoahObjToScanQueue* q, } } } + return strongly_marked; } template<> -inline void ShenandoahMark::mark_through_ref(oop *p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak) { - mark_non_generational_ref(p, q, mark_context, weak); +inline bool ShenandoahMark::mark_through_ref(oop *p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak) { + return mark_non_generational_ref(p, q, mark_context, weak); } template<> -inline void ShenandoahMark::mark_through_ref(narrowOop *p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak) { - mark_non_generational_ref(p, q, mark_context, weak); +inline bool ShenandoahMark::mark_through_ref(narrowOop *p, ShenandoahObjToScanQueue* q, ShenandoahObjToScanQueue* old_q, ShenandoahMarkingContext* const mark_context, bool weak) { + return mark_non_generational_ref(p, q, mark_context, weak); } template -inline void ShenandoahMark::mark_non_generational_ref(T* p, ShenandoahObjToScanQueue* q, +inline bool ShenandoahMark::mark_non_generational_ref(T* p, ShenandoahObjToScanQueue* q, ShenandoahMarkingContext* const mark_context, bool weak) { oop o = RawAccess<>::oop_load(p); + bool strongly_marked = false; if (!CompressedOops::is_null(o)) { oop obj = CompressedOops::decode_not_null(o); shenandoah_assert_not_forwarded(p, obj); shenandoah_assert_not_in_cset_except(p, obj, ShenandoahHeap::heap()->cancelled_gc()); - mark_ref(q, mark_context, weak, obj); + strongly_marked = mark_ref(q, mark_context, weak, obj); shenandoah_assert_marked(p, obj); } + return strongly_marked; } -inline void ShenandoahMark::mark_ref(ShenandoahObjToScanQueue* q, +inline bool ShenandoahMark::mark_ref(ShenandoahObjToScanQueue* q, ShenandoahMarkingContext* const mark_context, bool weak, oop obj) { bool skip_live = false; bool marked; + bool strongly_marked = false; if (weak) { marked = mark_context->mark_weak(obj); } else { marked = mark_context->mark_strong(obj, /* was_upgraded = */ skip_live); + strongly_marked = marked; } if (marked) { bool pushed = q->push(ShenandoahMarkTask(obj, skip_live, weak)); assert(pushed, "overflow queue should always succeed pushing"); } + return strongly_marked; } ShenandoahObjToScanQueueSet* ShenandoahMark::task_queues() const { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahObjectCountClosure.cpp b/src/hotspot/share/gc/shenandoah/shenandoahObjectCountClosure.cpp new file mode 100644 index 0000000000000..10ea9f35990fb --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahObjectCountClosure.cpp @@ -0,0 +1,22 @@ +#include "gc/shenandoah/shenandoahClosures.inline.hpp" +#include "gc/shenandoah/shenandoahObjectCountClosure.hpp" +#include "runtime/mutexLocker.hpp" + +#if INCLUDE_JFR + +void ShenandoahObjectCountClosure::merge_table(KlassInfoTable* global_cit) { + assert(_cit != nullptr, "The thread-local KlassInfoTable is not initialized"); + assert(global_cit != nullptr, "Shenandoah KlassInfoTable is not initialized"); + MutexLocker x(ObjectCountMerge_lock, Mutex::_no_safepoint_check_flag); + bool success = global_cit->merge(_cit); + + // Clear the _cit in the closure to ensure it won't be used again. + _cit = nullptr; + assert(success, "Failed to merge thread-local table"); +} + +bool ShenandoahObjectCountClosure::should_visit(oop o) { + return _filter->do_object_b(o); +} + +#endif // INCLUDE_JFR diff --git a/src/hotspot/share/gc/shenandoah/shenandoahObjectCountClosure.hpp b/src/hotspot/share/gc/shenandoah/shenandoahObjectCountClosure.hpp new file mode 100644 index 0000000000000..be95f1654559a --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahObjectCountClosure.hpp @@ -0,0 +1,48 @@ +#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHOBJECTCOUNTCLOSURE_HPP +#define SHARE_GC_SHENANDOAH_SHENANDOAHOBJECTCOUNTCLOSURE_HPP + +#include "memory/heapInspection.hpp" +#include "oops/access.hpp" +#include "oops/compressedOops.inline.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/mutex.hpp" + +#if INCLUDE_JFR + +class ShenandoahIsAliveClosure; + +class ShenandoahObjectCountClosure { +private: + KlassInfoTable* _cit; + ShenandoahIsAliveClosure* _filter; + + template + inline void do_oop_work(T* p) { + assert(p != nullptr, "Object is null"); + T o = RawAccess<>::oop_load(p); + assert(!CompressedOops::is_null(o), "CompressOops is null"); + oop obj = CompressedOops::decode_not_null(o); + assert(_cit != nullptr, "KlassInfoTable is null"); + if (should_visit(obj)) { + _cit->record_instance(obj); + } + } + +public: + ShenandoahObjectCountClosure(KlassInfoTable* cit, ShenandoahIsAliveClosure* is_alive) : _cit(cit), _filter(is_alive) {} + // Record the object's instance in the KlassInfoTable + inline void do_oop(narrowOop* o) { do_oop_work(o); } + // Record the object's instance in the KlassInfoTable + inline void do_oop(oop* o) { do_oop_work(o); } + inline KlassInfoTable* get_table() { return _cit; } + + bool should_visit(oop o); + + // Merges the heap's KlassInfoTable with the thread's KlassInfoTable. + // Clears the thread's table, so it won't be used again. + void merge_table(KlassInfoTable* global_cit); +}; + +#endif // INCLUDE_JFR + +#endif // SHARE_GC_SHENANDOAH_SHENANDOAHOBJECTCOUNTCLOSURE_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp index c2bfea664fdcf..29c9d00934a7e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp @@ -24,7 +24,7 @@ */ - +#include "gc/shared/objectCountEventSender.hpp" #include "gc/shared/strongRootsScope.hpp" #include "gc/shared/taskTerminator.hpp" #include "gc/shared/workerThread.hpp" @@ -32,6 +32,7 @@ #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationType.hpp" #include "gc/shenandoah/shenandoahMark.inline.hpp" +#include "gc/shenandoah/shenandoahObjectCountClosure.hpp" #include "gc/shenandoah/shenandoahReferenceProcessor.hpp" #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" #include "gc/shenandoah/shenandoahSTWMark.hpp" @@ -122,8 +123,23 @@ void ShenandoahSTWMark::mark_roots(uint worker_id) { auto queue = task_queues()->queue(worker_id); switch (_generation->type()) { case NON_GEN: { - ShenandoahMarkRefsClosure init_mark(queue, rp, nullptr); - _root_scanner.roots_do(&init_mark, worker_id); +#if INCLUDE_JFR + // Use object counting closure if ObjectCount or ObjectCountAfterGC event is enabled. + const bool object_count_enabled = ObjectCountEventSender::should_send_event(); + if (object_count_enabled) { + KlassInfoTable* const global_cit = ShenandoahHeap::heap()->get_cit(); + KlassInfoTable local_cit(false); + ShenandoahIsAliveClosure is_alive; + ShenandoahObjectCountClosure count(&local_cit, &is_alive); + ShenandoahMarkRefsAndCountClosure init_mark(queue, rp, nullptr, &count); + _root_scanner.roots_do(&init_mark, worker_id); + count.merge_table(global_cit); + } else +#endif // INCLUDE_JFR + { + ShenandoahMarkRefsClosure init_mark(queue, rp, nullptr); + _root_scanner.roots_do(&init_mark, worker_id); + } break; } case GLOBAL: { diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index 58e91217a3223..e9314434450ca 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -30,6 +30,7 @@ #include "oops/annotations.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.hpp" +#include "runtime/mutex.hpp" #include "utilities/macros.hpp" class ParallelObjectIterator; diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index 3f8915973e27d..b6bd415b279df 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -85,6 +85,7 @@ Monitor* InitCompleted_lock = nullptr; Monitor* BeforeExit_lock = nullptr; Monitor* Notify_lock = nullptr; Mutex* ExceptionCache_lock = nullptr; +Mutex* ObjectCountMerge_lock = nullptr; Mutex* TrainingData_lock = nullptr; Monitor* TrainingReplayQueue_lock = nullptr; #ifndef PRODUCT @@ -246,6 +247,7 @@ void mutex_init() { MUTEX_DEFN(SignatureHandlerLibrary_lock , PaddedMutex , safepoint); MUTEX_DEFN(SymbolArena_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(ExceptionCache_lock , PaddedMutex , safepoint); + MUTEX_DEFN(ObjectCountMerge_lock , PaddedMutex , nosafepoint); #ifndef PRODUCT MUTEX_DEFN(FullGCALot_lock , PaddedMutex , safepoint); // a lock to make FullGCALot MT safe #endif diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp index f888c789eb738..d08c757a34dc3 100644 --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -87,6 +87,7 @@ extern Monitor* InitCompleted_lock; // a lock used to signal thread extern Monitor* BeforeExit_lock; // a lock used to guard cleanups and shutdown hooks extern Monitor* Notify_lock; // a lock used to synchronize the start-up of the vm extern Mutex* ExceptionCache_lock; // a lock used to synchronize exception cache updates +extern Mutex* ObjectCountMerge_lock; // a lock used to synchronize merging a thread-local object count histogram into a global one #ifndef PRODUCT extern Mutex* FullGCALot_lock; // a lock to make FullGCALot MT safe diff --git a/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java b/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java new file mode 100644 index 0000000000000..ea164370fa3e0 --- /dev/null +++ b/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java @@ -0,0 +1,62 @@ +package jdk.jfr.event.gc.objectcount; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +public class ObjectCountEvent { + private static final String objectCountEventPath = EventNames.ObjectCount; + private static final String gcEventPath = EventNames.GarbageCollection; + private static final String heapSummaryEventPath = EventNames.GCHeapSummary; + + public static void test(String gcName) throws Exception { + Recording recording = new Recording(); + recording.enable(objectCountEventPath); + recording.enable(gcEventPath); + recording.enable(heapSummaryEventPath); + + ObjectCountEventVerifier.createTestData(); + recording.start(); + System.gc(); + recording.stop(); + + System.out.println("gcName=" + gcName); + List events = Events.fromRecording(recording); + for (RecordedEvent event : events) { + System.out.println("Event: " + event); + } + + Optional gcEvent = events.stream() + .filter(e -> isMySystemGc(e, gcName)) + .findFirst(); + + List objCountEvents = events.stream() + .filter(e -> Events.isEventType(e, objectCountEventPath)) + .collect(Collectors.toList()); + Asserts.assertFalse(objCountEvents.isEmpty(), "No objCountEvents"); + + Optional heapSummaryEvent = events.stream() + .filter(e -> Events.isEventType(e, heapSummaryEventPath)) + .filter(e -> "After GC".equals(Events.assertField(e, "when").getValue())) + .findFirst(); + Asserts.assertTrue(heapSummaryEvent.isPresent(), "No heapSummary"); + System.out.println("Found heapSummaryEvent: " + heapSummaryEvent.get()); + + Events.assertField(heapSummaryEvent.get(), "heapUsed").atLeast(0L).getValue(); + ObjectCountEventVerifier.verify(objCountEvents); + } + + private static boolean isMySystemGc(RecordedEvent event, String gcName) { + return Events.isEventType(event, gcEventPath) && + gcName.equals(Events.assertField(event, "name").getValue()) && + "System.gc()".equals(Events.assertField(event, "cause").getValue()); + } +} diff --git a/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountAfterGCEventWithShenandoah.java b/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountAfterGCEventWithShenandoah.java new file mode 100644 index 0000000000000..42eef5b12f5d8 --- /dev/null +++ b/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountAfterGCEventWithShenandoah.java @@ -0,0 +1,17 @@ +package jdk.jfr.event.gc.objectcount; +import jdk.test.lib.jfr.GCHelper; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @requires (vm.gc == "Shenandoah" | vm.gc == null) + * & vm.opt.ExplicitGCInvokesConcurrent != true + * @library /test/lib /test/jdk + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseShenandoahGC -XX:MarkSweepDeadRatio=0 -XX:-UseCompressedOops -XX:-UseCompressedClassPointers -XX:+IgnoreUnrecognizedVMOptions jdk.jfr.event.gc.objectcount.TestObjectCountAfterGCEventWithShenandoah + */ +public class TestObjectCountAfterGCEventWithShenandoah { + public static void main(String[] args) throws Exception { + ObjectCountAfterGCEvent.test(GCHelper.gcShenandoah); + } +} diff --git a/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java b/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java new file mode 100644 index 0000000000000..3191ebb43d3f2 --- /dev/null +++ b/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java @@ -0,0 +1,17 @@ +package jdk.jfr.event.gc.objectcount; +import jdk.test.lib.jfr.GCHelper; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @requires (vm.gc == "Shenandoah" | vm.gc == null) + * & vm.opt.ExplicitGCInvokesConcurrent != false + * @library /test/lib /test/jdk + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+ExplicitGCInvokesConcurrent -XX:MarkSweepDeadRatio=0 -XX:-UseCompressedOops -XX:-UseCompressedClassPointers -XX:+IgnoreUnrecognizedVMOptions jdk.jfr.event.gc.objectcount.TestObjectCountEventWithShenandoah + */ +public class TestObjectCountEventWithShenandoah { + public static void main(String[] args) throws Exception { + ObjectCountEvent.test(GCHelper.gcShenandoah); + } +} diff --git a/test/lib/jdk/test/lib/jfr/GCHelper.java b/test/lib/jdk/test/lib/jfr/GCHelper.java index 07c6c1e1ce550..4b30e79ba0d43 100644 --- a/test/lib/jdk/test/lib/jfr/GCHelper.java +++ b/test/lib/jdk/test/lib/jfr/GCHelper.java @@ -74,6 +74,7 @@ public class GCHelper { public static final String gcPSMarkSweep = "PSMarkSweep"; public static final String gcParallelOld = "ParallelOld"; public static final String pauseLevelEvent = "GCPhasePauseLevel"; + public static final String gcShenandoah = "Shenandoah"; private static final List g1HeapRegionTypes; private static final List shenandoahHeapRegionStates;