From be59b7decea660a05c021ff7676287f2e5605236 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 20 Jan 2026 16:23:18 +0100 Subject: [PATCH] wip --- .../jfr/leakprofiler/chains/dfsClosure.cpp | 186 ++++++++++-------- .../jfr/leakprofiler/chains/dfsClosure.hpp | 12 +- 2 files changed, 115 insertions(+), 83 deletions(-) diff --git a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp index cb8ea06c7a7..5250f7d09ae 100644 --- a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp @@ -61,6 +61,11 @@ char txt[1024]; \ stringStream ss(txt, sizeof(txt)); \ o->klass()->name()->print_value_on(&ss); \ + if (o->is_objArray()) { \ + const objArrayOop pointee_oa = (objArrayOop) o; \ + const int array_len = pointee_oa->length(); \ + ss.print(" [%d]", array_len); \ + } \ printf("%s: " PTR_FORMAT " %s ", prefix, p2i(o), txt); \ printf("\n"); \ fflush(stdout); \ @@ -72,6 +77,11 @@ char txt[1024]; \ stringStream ss(txt, sizeof(txt)); \ o->klass()->name()->print_value_on(&ss); \ + if (o->is_objArray()) { \ + const objArrayOop pointee_oa = (objArrayOop) o; \ + const int array_len = pointee_oa->length(); \ + ss.print(" [%d]", array_len); \ + } \ printf("%s: " PTR_FORMAT " %s ", prefix, p2i(o), txt); \ printf(format, __VA_ARGS__); \ printf("\n"); \ @@ -101,7 +111,9 @@ void DFSClosure::find_leaks_from_root_set(EdgeStore* edge_store, TRC("SCANNING ROOTS"); - // Mark root set, to avoid going sideways + // Mark root set, to avoid going sideways. The intent here is to prevent + // long reference chains that would be caused by tracing through multiple root + // objects. DFSClosure dfs(edge_store, mark_bits, nullptr); dfs._max_depth = 1; RootSetClosure rs(&dfs); @@ -138,7 +150,10 @@ DFSClosure::DFSClosure(EdgeStore* edge_store, JFRBitSet* mark_bits, const Edge* _max_depth(max_dfs_depth), _ignore_root_set(false), // _probe_stack(1024, 4, max_probe_stack_elems), _probe_stack(), - _current_item(nullptr) + _current_ref(UnifiedOopRef::encode_null()), + _current_pointee(nullptr), + _current_depth(0), + _current_chunkindex(0) { } @@ -150,10 +165,11 @@ DFSClosure::~DFSClosure() { } #endif // ASSERT -void DFSClosure::push_to_probe_stack(UnifiedOopRef ref, oop pointee, size_t depth, int chunkindex) { +void DFSClosure::probe_stack_push(UnifiedOopRef ref, oop pointee, size_t depth) { + + assert(!ref.is_null(), "invariant"); if (pointee == nullptr) { - // Don't push null references return; } @@ -167,74 +183,100 @@ void DFSClosure::push_to_probe_stack(UnifiedOopRef ref, oop pointee, size_t dept return; } - ProbeStackItem item { ref, checked_cast(depth), chunkindex }; + ProbeStackItem item { ref, checked_cast(depth), 0 }; _probe_stack.push(item); TRCOOPFMT("pushed", pointee, "path depth %u, probestack depth %zu", item.depth, _probe_stack.size()); } +void DFSClosure::probe_stack_push_followup_chunk(UnifiedOopRef ref, oop pointee, size_t depth, int chunkindex) { + + assert(!ref.is_null(), "invariant"); + assert(pointee != nullptr, "invariant"); + assert(chunkindex > 0, "invariant"); + + if (_probe_stack.is_full()) { + // Probe stack exhausted; see remarks about probe stack max depth above. + return; + } + + ProbeStackItem item { ref, checked_cast(depth), chunkindex }; + _probe_stack.push(item); + +TRCOOPFMT("pushed", pointee, "path depth %u, probestack depth %zu, chunkindex %d (follow-up)", item.depth, _probe_stack.size(), chunkindex); + +} + +bool DFSClosure::probe_stack_pop() { + + if (_probe_stack.is_empty()) { + _current_ref = UnifiedOopRef::encode_null(); + _current_pointee = nullptr; + _current_depth = 0; + _current_chunkindex = 0; + return false; + } + + ProbeStackItem item = _probe_stack.pop(); + _current_ref = item.r; + assert(!_current_ref.is_null(), "invariant"); + _current_depth = item.depth; + assert(_current_depth < _max_depth, "invariant"); + _current_chunkindex = item.chunkindex; + assert(_current_chunkindex >= 0, "invariant"); + + _current_pointee = _current_ref.dereference(); + +TRCOOPFMT("popped", _current_pointee, "path depth %zu, probestack depth %zu, chunkindex %d%s", _current_depth, _probe_stack.size(), _current_chunkindex, _current_chunkindex > 0 ? " (followup)" : ""); + + return true; +} + void DFSClosure::handle_oop() { - assert(_current_item != nullptr, "Sanity"); - assert(_current_item->chunkindex == 0, "Sanity"); - const oop pointee = current_pointee(); - const size_t depth = current_depth(); - assert(depth < _max_depth, "Sanity (%zu)", depth); - - -TRCOOPFMT("popped", pointee, "path depth %zu, probestack depth %zu", depth, _probe_stack.size()); - - if (depth == 0 && _ignore_root_set) { - assert(pointee_was_visited(pointee), "We should have already visited roots"); - _reference_stack[depth] = _current_item->r; + if (_current_depth == 0 && _ignore_root_set) { + assert(pointee_was_visited(_current_pointee), "We should have already visited roots"); + _reference_stack[_current_depth] = _current_ref; // continue since we want to process children, too } else { - if (pointee_was_visited(pointee)) { + if (pointee_was_visited(_current_pointee)) { return; // already processed } - mark_pointee_as_visited(pointee); - _reference_stack[depth] = _current_item->r; - if (pointee_was_sampled(pointee)) { -TRC("=> SAMPLE OBJECT FOUND"); + mark_pointee_as_visited(_current_pointee); + _reference_stack[_current_depth] = _current_ref; + if (pointee_was_sampled(_current_pointee)) { +TRC("=> SAMPLE OBJECT FOUND (handle_oop)"); add_chain(); } } // trace children if needed - if (depth == _max_depth - 1) { + if (_current_depth == _max_depth - 1) { return; // stop following this chain } - pointee->oop_iterate(this); + _current_pointee->oop_iterate(this); } void DFSClosure::handle_objarrayoop() { - assert(_current_item != nullptr, "Sanity"); -ShouldNotReachHere(); - const oop pointee = current_pointee(); - const size_t depth = current_depth(); - assert(depth < _max_depth, "Sanity"); - - const int chunkindex = _current_item->chunkindex; - assert(chunkindex >= 0, "Sanity"); - - if (current_depth() == 0 && _ignore_root_set) { - assert(pointee_was_visited(pointee), "We should have already visited roots"); - _reference_stack[depth] = _current_item->r; + if (_current_depth == 0 && _ignore_root_set) { + assert(pointee_was_visited(_current_pointee), "We should have already visited roots"); + _reference_stack[_current_depth] = _current_ref; // continue since we want to process children, too } else { - if (chunkindex == 0) { + if (_current_chunkindex == 0) { // For the first chunk only, check, process and mark the array oop itself. - if (pointee_was_visited(pointee)) { + if (pointee_was_visited(_current_pointee)) { return; // already processed } - mark_pointee_as_visited(pointee); - _reference_stack[depth] = _current_item->r; + mark_pointee_as_visited(_current_pointee); + _reference_stack[_current_depth] = _current_ref; - if (pointee_was_sampled(pointee)) { + if (pointee_was_sampled(_current_pointee)) { +TRC("=> SAMPLE OBJECT FOUND (handle_objarrayoop)"); add_chain(); } } @@ -242,62 +284,49 @@ ShouldNotReachHere(); // trace children if needed - if (depth == _max_depth - 1) { + if (_current_depth == _max_depth - 1) { return; // stop following this chain } - const objArrayOop pointee_oa = (objArrayOop) pointee; + const objArrayOop pointee_oa = (objArrayOop) _current_pointee; const int array_len = pointee_oa->length(); - const int begidx = chunkindex * array_chunk_size; - const int endidx = MIN2(array_len, (chunkindex + 1) * array_chunk_size); + if (array_len == 0) { + return; + } + const int begidx = _current_chunkindex * array_chunk_size; + const int endidx = MIN2(array_len, (_current_chunkindex + 1) * array_chunk_size); + assert(begidx < endidx, "invariant"); - // Push follow-up chunk: same reference, same depth, next chunk index. - // Do this before pushing the child references to preserve depth-first - // traversal. + // Push follow-up chunk if (endidx < array_len) { - push_to_probe_stack(_current_item->r, pointee, depth, chunkindex + 1); + probe_stack_push_followup_chunk(_current_ref, _current_pointee, _current_depth, _current_chunkindex + 1); } // push child references - if (begidx < endidx) { - pointee_oa->oop_iterate_range(this, begidx, endidx); - } + pointee_oa->oop_iterate_range(this, begidx, endidx); } void DFSClosure::drain_probe_stack() { DEBUG_ONLY(unsigned last_depth = 0;) - while (!_probe_stack.is_empty() && - !GranularTimer::is_finished()) { + while (probe_stack_pop() && !GranularTimer::is_finished()) { - const ProbeStackItem item = _probe_stack.pop(); - assert(item.depth <= (last_depth + 1), "jumping nodes?"); + // We should not dive downward more than 1 indirection. + assert(_current_depth <= (last_depth + 1), "invariant"); - // anchor current item - _current_item = &item; - - assert(!_current_item->r.is_null(), "invariant"); - assert(current_pointee() != nullptr, "invariant"); - - //if (current_pointee()->is_objArray()) { -if (false) { + if (_current_pointee->is_objArray()) { handle_objarrayoop(); } else { handle_oop(); } - DEBUG_ONLY(last_depth = item.depth;) - - // reset current item - _current_item = nullptr; - + DEBUG_ONLY(last_depth = _current_depth;) } } void DFSClosure::add_chain() { - const size_t depth = current_depth(); - const size_t array_length = depth + 2; + const size_t array_length = _current_depth + 2; ResourceMark rm; Edge* const chain = NEW_RESOURCE_ARRAY(Edge, array_length); @@ -305,7 +334,7 @@ void DFSClosure::add_chain() { if (UseNewCode) { TRC("---- reference stack ----"); - for (size_t i = 0; i <= depth; i++) { + for (size_t i = 0; i <= _current_depth; i++) { const oop pointee = _reference_stack[i].dereference(); TRCOOP("", pointee); } @@ -313,12 +342,12 @@ if (UseNewCode) { } // aggregate from depth-first search - for (size_t i = 0; i <= depth; i++) { + for (size_t i = 0; i <= _current_depth; i++) { const size_t next = idx + 1; - const size_t d = depth - i; + const size_t d = _current_depth - i; chain[idx++] = Edge(&chain[next], _reference_stack[d]); } - assert(depth + 1 == idx, "invariant"); + assert(_current_depth + 1 == idx, "invariant"); assert(array_length == idx + 1, "invariant"); // aggregate from breadth-first search @@ -334,20 +363,19 @@ void DFSClosure::do_oop(oop* ref) { assert(ref != nullptr, "invariant"); assert(is_aligned(ref, HeapWordSize), "invariant"); const oop pointee = HeapAccess::oop_load(ref); - push_to_probe_stack(UnifiedOopRef::encode_in_heap(ref), pointee, current_depth() + 1, 0); + probe_stack_push(UnifiedOopRef::encode_in_heap(ref), pointee, _current_depth + 1); } void DFSClosure::do_oop(narrowOop* ref) { assert(ref != nullptr, "invariant"); assert(is_aligned(ref, sizeof(narrowOop)), "invariant"); const oop pointee = HeapAccess::oop_load(ref); - push_to_probe_stack(UnifiedOopRef::encode_in_heap(ref), pointee, current_depth() + 1, 0); + probe_stack_push(UnifiedOopRef::encode_in_heap(ref), pointee, _current_depth + 1); } void DFSClosure::do_root(UnifiedOopRef ref) { assert(!ref.is_null(), "invariant"); const oop pointee = ref.dereference(); assert(pointee != nullptr, "invariant"); - assert(_current_item == nullptr, "invariant"); - push_to_probe_stack(ref, pointee, 0, 0); + probe_stack_push(ref, pointee, 0); } diff --git a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp index 7c967fec27d..5dd0f110c91 100644 --- a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp @@ -61,9 +61,11 @@ class DFSClosure : public BasicOopIterateClosure { }; Stack _probe_stack; - const ProbeStackItem* _current_item; - oop current_pointee() const { return _current_item->r.dereference(); } - size_t current_depth() const { return _current_item == nullptr ? 0 : _current_item->depth; } + // Walkstate + UnifiedOopRef _current_ref; + oop _current_pointee; + size_t _current_depth; + int _current_chunkindex; bool pointee_was_visited(const oop pointee) const { return _mark_bits->is_marked(pointee); } void mark_pointee_as_visited(const oop pointee) { _mark_bits->mark_obj(pointee); } @@ -73,7 +75,9 @@ class DFSClosure : public BasicOopIterateClosure { void handle_oop(); void handle_objarrayoop(); - void push_to_probe_stack(UnifiedOopRef ref, oop pointee, size_t depth, int chunkindex); + void probe_stack_push_followup_chunk(UnifiedOopRef ref, oop pointee, size_t depth, int chunkindex); + void probe_stack_push(UnifiedOopRef ref, oop pointee, size_t depth); + bool probe_stack_pop(); public: virtual ReferenceIterationMode reference_iteration_mode() { return DO_FIELDS_EXCEPT_REFERENT; }