diff --git a/src/hotspot/share/gc/shared/gcCause.cpp b/src/hotspot/share/gc/shared/gcCause.cpp index 489a5d32a9033..0eb8b31eabc73 100644 --- a/src/hotspot/share/gc/shared/gcCause.cpp +++ b/src/hotspot/share/gc/shared/gcCause.cpp @@ -95,6 +95,9 @@ const char* GCCause::to_string(GCCause::Cause cause) { case _shenandoah_allocation_failure_evac: return "Allocation Failure During Evacuation"; + case _shenandoah_jfr_object_count: + return "JFR Object Count Event Enabled"; + case _shenandoah_humongous_allocation_failure: return "Humongous Allocation Failure"; diff --git a/src/hotspot/share/gc/shared/gcCause.hpp b/src/hotspot/share/gc/shared/gcCause.hpp index f775d41340dbf..d71ed894c6dc9 100644 --- a/src/hotspot/share/gc/shared/gcCause.hpp +++ b/src/hotspot/share/gc/shared/gcCause.hpp @@ -76,6 +76,7 @@ class GCCause : public AllStatic { _shenandoah_humongous_allocation_failure, _shenandoah_concurrent_gc, _shenandoah_upgrade_to_full_gc, + _shenandoah_jfr_object_count, _z_timer, _z_warmup, diff --git a/src/hotspot/share/gc/shared/gcTrace.hpp b/src/hotspot/share/gc/shared/gcTrace.hpp index 115b618795597..c087b891cc5bc 100644 --- a/src/hotspot/share/gc/shared/gcTrace.hpp +++ b/src/hotspot/share/gc/shared/gcTrace.hpp @@ -105,8 +105,8 @@ class GCTracer { void report_gc_reference_stats(const ReferenceProcessorStats& rp) const; // Sends event data to the ObjectCount and/or ObjectCountAfterGC event - template - void report_object_count(T* heap) NOT_SERVICES_RETURN; + template + void report_object_count() NOT_SERVICES_RETURN; void report_object_count_after_gc(BoolObjectClosure* object_filter, WorkerThreads* workers) NOT_SERVICES_RETURN; void report_cpu_time_event(double user_time, double system_time, double real_time) const; diff --git a/src/hotspot/share/gc/shared/gcTrace.inline.hpp b/src/hotspot/share/gc/shared/gcTrace.inline.hpp index b243c4f068bab..28fc9926a0694 100644 --- a/src/hotspot/share/gc/shared/gcTrace.inline.hpp +++ b/src/hotspot/share/gc/shared/gcTrace.inline.hpp @@ -27,8 +27,10 @@ class ObjectCountEventSenderClosure : public KlassInfoClosure { virtual void do_cinfo(KlassInfoEntry* entry) { if (should_send_event(entry)) { ObjectCountEventSender::send(entry, _timestamp); - _cit->delete_entry(entry, &_total_size_in_words); } + // Delete the entry even if we don't send it. This ensure live objects that + // weren't sent in a previous event emission are not monotonically increasing. + _cit->delete_entry(entry); } private: @@ -38,17 +40,19 @@ class ObjectCountEventSenderClosure : public KlassInfoClosure { } }; -template -void GCTracer::report_object_count(T* heap) { - if (!ObjectCountEventSender::should_send_event()) { +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); + ObjectCountEventSenderClosure event_sender(cit->size_of_instances_in_words(), Ticks::now(), cit); cit->iterate(&event_sender); + cit->reset_size_of_instances_in_words(); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index dbd917e887358..4c3a9daa25068 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -318,7 +318,7 @@ void ShenandoahConcurrentGC::vmop_entry_final_mark() { // Do not report object count during a safepoint assert(!ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Should not be at safepoint"); - heap->tracer()->report_object_count(heap); + heap->tracer()->report_object_count(); } void ShenandoahConcurrentGC::vmop_entry_init_update_refs() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp index d81c49363a230..cd93c79f4911a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp @@ -57,7 +57,8 @@ class ShenandoahConcurrentGC : public ShenandoahGC { public: ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap); - bool collect(GCCause::Cause cause) override; + // ShenandoahJFRObjectCountGC will override ShenandoahConcurrentGC's collect method if enabled. + virtual bool collect(GCCause::Cause cause) override; ShenandoahDegenPoint degen_point() const; void entry_concurrent_update_refs_prepare(ShenandoahHeap* heap); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp index 2623ecba372cc..35dbeab8bfde8 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp @@ -174,7 +174,8 @@ void ShenandoahMarkConcurrentRootsTask::work(uint worker_id) { nullptr : _old_queue_set->queue(worker_id); // Use object counting closure if ObjectCountAfterGC event is enabled - bool object_count_enabled = ObjectCountEventSender::should_send_event(); + const bool object_count_enabled = ObjectCountEventSender::should_send_event() || + ObjectCountEventSender::should_send_event(); if (object_count_enabled) { KlassInfoTable* const main_cit = ShenandoahHeap::heap()->get_cit(); KlassInfoTable local_cit(false); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 27a7ff54f364c..1fafcd02a2afd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -23,6 +23,8 @@ * */ +#include "gc/shared/gcTrace.inline.hpp" +#include "gc/shared/objectCountEventSender.inline.hpp" #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" @@ -34,6 +36,7 @@ #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" +#include "gc/shenandoah/shenandoahJfrObjectCountGC.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "logging/log.hpp" #include "memory/metaspaceStats.hpp" @@ -48,6 +51,8 @@ ShenandoahControlThread::ShenandoahControlThread() : } void ShenandoahControlThread::run_service() { + jlong jfr_event_timestamp = os::javaTimeMillis(); + ShenandoahHeap* const heap = ShenandoahHeap::heap(); const GCMode default_mode = concurrent_normal; const GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc; @@ -174,6 +179,9 @@ void ShenandoahControlThread::run_service() { // If this cycle completed without being cancelled, notify waiters about it if (!heap->cancelled_gc()) { + if (ObjectCountEventSender::should_send_event()) { + jfr_event_timestamp = os::javaTimeMillis(); + } notify_alloc_failure_waiters(); } @@ -229,6 +237,24 @@ void ShenandoahControlThread::run_service() { // Print Metaspace change following GC (if logging is enabled). MetaspaceUtils::print_metaspace_change(meta_sizes); + } else { + if (ObjectCountEventSender::should_send_event() && + (os::javaTimeMillis() - jfr_event_timestamp > (jlong) ShenandoahJFRObjectCountInterval)) { + GCIdMark gc_id_mark; + + // Do not increment the GC ID: + // size_t current_gc_id = get_gc_id(); + // GCIdMark gc_id_mark(get_gc_id()); + + const GCCause::Cause count_cause = GCCause::_shenandoah_jfr_object_count; + service_object_count_cycle(count_cause); + + // Reset GC Cycle + heap->phase_timings()->flush_par_workers_to_cycle(); + heap->phase_timings()->flush_cycle_to_global(); + + jfr_event_timestamp = os::javaTimeMillis(); + } } // Check if we have seen a new target for soft max heap size or if a gc was requested. @@ -253,7 +279,6 @@ void ShenandoahControlThread::run_service() { } os::naked_short_sleep(sleep); } - heap->set_cit(nullptr); } @@ -362,6 +387,22 @@ void ShenandoahControlThread::service_stw_degenerated_cycle(GCCause::Cause cause gc.collect(cause); } +void ShenandoahControlThread::service_object_count_cycle(GCCause::Cause cause) { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + + // Create a GC Session so marking will work + ShenandoahGCSession session(cause, heap->global_generation()); + + TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); + + ShenandoahJfrObjectCountGC gc(heap->global_generation()); + + if (gc.collect(cause)) { + heap->tracer()->report_object_count(); + } +} + + void ShenandoahControlThread::request_gc(GCCause::Cause cause) { if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { handle_requested_gc(cause); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp index 9d95b5df7ed30..c0b44c6baac2d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp @@ -61,6 +61,8 @@ class ShenandoahControlThread: public ShenandoahController { void service_concurrent_normal_cycle(GCCause::Cause cause); void service_stw_full_cycle(GCCause::Cause cause); void service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point); + // Service cycle for counting live objects + void service_object_count_cycle(GCCause::Cause cause); void notify_gc_waiters(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahJfrObjectCountGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahJfrObjectCountGC.cpp new file mode 100644 index 0000000000000..4bb3481a6ca88 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahJfrObjectCountGC.cpp @@ -0,0 +1,57 @@ +#include "gc/shenandoah/shenandoahHeap.inline.hpp" +#include "gc/shenandoah/shenandoahJfrObjectCountGC.hpp" +#include "prims/jvmtiTagMap.hpp" +#include "gc/shenandoah/shenandoahVerifier.hpp" +#include "gc/shenandoah/shenandoahPhaseTimings.hpp" +#include "gc/shenandoah/shenandoahUtils.hpp" + +void ShenandoahJfrObjectCountGC::op_final_mark() { + ShenandoahHeap* const heap = ShenandoahHeap::heap(); + assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Should be at safepoint"); + assert(!heap->has_forwarded_objects(), "No forwarded objects on this path"); + + if (ShenandoahVerify) { + heap->verifier()->verify_roots_no_forwarded(); + } + + if (!heap->cancelled_gc()) { + _mark.finish_mark(); + + // Notify JVMTI that the tagmap table will need cleaning. + JvmtiTagMap::set_needs_cleaning(); + + if (ShenandoahVerify) { + ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_mark_verify); + heap->verifier()->verify_after_concmark(); + } + } + + { + ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::final_mark_propagate_gc_state); + heap->propagate_gc_state_to_all_threads(); + } +} + +bool ShenandoahJfrObjectCountGC::collect(GCCause::Cause cause) { + ShenandoahHeap* const heap = ShenandoahHeap::heap(); + + entry_reset(); + + vmop_entry_init_mark(); + + entry_scan_remembered_set(); + + entry_mark_roots(); + if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_roots)) { + return false; + } + + entry_mark(); + if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark)) { + return false; + } + + vmop_entry_final_mark(); + entry_reset_after_collect(); + return true; +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahJfrObjectCountGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahJfrObjectCountGC.hpp new file mode 100644 index 0000000000000..a7479093f534b --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahJfrObjectCountGC.hpp @@ -0,0 +1,25 @@ +#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHOBJECTCOUNTGC_HPP +#define SHARE_GC_SHENANDOAH_SHENANDOAHOBJECTCOUNTGC_HPP + +#include "gc/shared/gcCause.hpp" +#include "gc/shenandoah/shenandoahConcurrentGC.hpp" +#include "gc/shenandoah/shenandoahConcurrentMark.hpp" +#include "gc/shenandoah/shenandoahGC.hpp" +#include "gc/shenandoah/shenandoahHeap.hpp" + +// A pseudo Shenandoah GC where only the marking phase is initiated +// without any memory reclamation. This GC is used specifically for +// the Java Flight Recorder's ObjectCount event. +class ShenandoahJfrObjectCountGC : public ShenandoahConcurrentGC { + public: + ShenandoahJfrObjectCountGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap = false) + : ShenandoahConcurrentGC(generation, do_old_gc_bootstrap) {} + + // Only make use of the mark phase in Shenandoah and does not reclaim any garbage + bool collect(GCCause::Cause cause) override; + + protected: + void op_final_mark() override; +}; + +#endif // SHARE_GC_SHENANDOAH_SHENANDOAHOBJECTCOUNTGC_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index 3b8dcc43d3759..5903032af0fdf 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -70,7 +70,8 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, ShenandoahRefe mark_loop_work(&cl, ld, w, t, req); } else { // Use object counting closure if ObjectCountAfterGC event is enabled - bool object_count_enabled = ObjectCountEventSender::should_send_event(); + const bool object_count_enabled = ObjectCountEventSender::should_send_event() || + ObjectCountEventSender::should_send_event(); if (object_count_enabled) { KlassInfoTable* const main_cit = ShenandoahHeap::heap()->get_cit(); KlassInfoTable local_cit(false); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp index 613e2f44edb76..17812649d166a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp @@ -124,7 +124,8 @@ void ShenandoahSTWMark::mark_roots(uint worker_id) { switch (_generation->type()) { case NON_GEN: { // Use object counting closure if ObjectCountAfterGC event is enabled - bool object_count_enabled = ObjectCountEventSender::should_send_event(); + const bool object_count_enabled = ObjectCountEventSender::should_send_event() || + ObjectCountEventSender::should_send_event(); if (object_count_enabled) { KlassInfoTable* const main_cit = ShenandoahHeap::heap()->get_cit(); KlassInfoTable local_cit(false); diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index c51f4f164895d..48e3585b8ccc1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -530,6 +530,11 @@ "to prevent starvation of the old collector. Setting this to " \ "0 will allow back to back young collections to run during old " \ "collections.") \ + \ + product(uintx, ShenandoahJFRObjectCountInterval, 0, \ + "Duration in milliseconds to run object counting GC cycles. " \ + "0 means disabled.") \ + range(0, max_uintx) \ // end of GC_SHENANDOAH_FLAGS #endif // SHARE_GC_SHENANDOAH_SHENANDOAH_GLOBALS_HPP diff --git a/src/hotspot/share/memory/heapInspection.cpp b/src/hotspot/share/memory/heapInspection.cpp index 3350104ceaf6a..d728b85640f0e 100644 --- a/src/hotspot/share/memory/heapInspection.cpp +++ b/src/hotspot/share/memory/heapInspection.cpp @@ -280,11 +280,13 @@ bool KlassInfoTable::merge_entry(const KlassInfoEntry* cie) { return false; } -void KlassInfoTable::delete_entry(KlassInfoEntry* entry, size_t* total_table_size) { +void KlassInfoTable::reset_size_of_instances_in_words() { + _size_of_instances_in_words = 0; +} + +void KlassInfoTable::delete_entry(KlassInfoEntry* entry) { uint idx = hash(entry->klass()) % _num_buckets; - size_t total_entry_size = entry->words(); _buckets[idx].remove_from_list(entry); - *total_table_size -= total_entry_size; } class KlassInfoTableMergeClosure : public KlassInfoClosure { diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index 5058e29a8dacc..3371bef0a0393 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -132,7 +132,11 @@ class KlassInfoTable: public StackObj { bool merge(KlassInfoTable* table); bool merge_entry(const KlassInfoEntry* cie); // Deletes the KlassInfoEntry in the list - void delete_entry(KlassInfoEntry* entry, size_t* total_table_size); + void delete_entry(KlassInfoEntry* entry); + // Reset the size of instances in words to 0. Only call this method + // after sending the KlassInfoTable for the ObjectCount or + // ObjectCountAfterGC event and deleting it's entries. + void reset_size_of_instances_in_words(); friend class KlassInfoHisto; friend class KlassHierarchy; }; diff --git a/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java b/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java index 30d9e141d8285..aa6862b10fba4 100644 --- a/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java +++ b/test/jdk/jdk/jfr/event/gc/objectcount/ObjectCountEvent.java @@ -27,12 +27,11 @@ public static void test(String gcName) throws Exception { recording.start(); Path tempFile = Path.of("temp.jfr"); + Asserts.assertFalse(Files.exists(tempFile), "File already exists."); recording.dump(tempFile); // Forces chunk rotation System.gc(); recording.stop(); - Files.deleteIfExists(tempFile); - System.out.println("gcName=" + gcName); List events = Events.fromRecording(recording); for (RecordedEvent event : events) { diff --git a/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java b/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java index 3191ebb43d3f2..af589a9c30b30 100644 --- a/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java +++ b/test/jdk/jdk/jfr/event/gc/objectcount/TestObjectCountEventWithShenandoah.java @@ -8,7 +8,7 @@ * @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 + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:ShenandoahJFRObjectCountInterval=300 -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 {