diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp deleted file mode 100644 index a4817cbc87d..00000000000 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" -#include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp" -#include "jfr/utilities/jfrTypes.hpp" -#include "runtime/handles.inline.hpp" -#include "runtime/jniHandles.inline.hpp" -#include "runtime/safepoint.hpp" -#include "runtime/semaphore.hpp" -#include "utilities/growableArray.hpp" - -static const int initial_array_size = 30; - -class ThreadGroupExclusiveAccess : public StackObj { - private: - static Semaphore _mutex_semaphore; - public: - ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); } - ~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); } -}; - -Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1); -JfrThreadGroup* JfrThreadGroup::_instance = nullptr; - -class JfrThreadGroupPointers : public ResourceObj { - private: - const Handle _thread_group_handle; - jweak _thread_group_weak_ref; - public: - JfrThreadGroupPointers(Handle thread_group_handle, jweak thread_group_weak_ref); - Handle thread_group_handle() const; - jweak thread_group_weak_ref() const; - oopDesc* thread_group_oop() const; - jweak transfer_weak_global_handle_ownership(); - void clear_weak_ref(); -}; - -JfrThreadGroupPointers::JfrThreadGroupPointers(Handle thread_group_handle, jweak thread_group_weak_ref) : - _thread_group_handle(thread_group_handle), - _thread_group_weak_ref(thread_group_weak_ref) {} - -Handle JfrThreadGroupPointers::thread_group_handle() const { - return _thread_group_handle; -} - -jweak JfrThreadGroupPointers::thread_group_weak_ref() const { - return _thread_group_weak_ref; -} - -oopDesc* JfrThreadGroupPointers::thread_group_oop() const { - assert(_thread_group_weak_ref == nullptr || - JNIHandles::resolve_non_null(_thread_group_weak_ref) == _thread_group_handle(), "invariant"); - return _thread_group_handle(); -} - -jweak JfrThreadGroupPointers::transfer_weak_global_handle_ownership() { - jweak temp = _thread_group_weak_ref; - _thread_group_weak_ref = nullptr; - return temp; -} - -void JfrThreadGroupPointers::clear_weak_ref() { - if (nullptr != _thread_group_weak_ref) { - JNIHandles::destroy_weak_global(_thread_group_weak_ref); - } -} - -class JfrThreadGroupsHelper : public ResourceObj { - private: - static const int invalid_iterator_pos = -1; - GrowableArray* _thread_group_hierarchy; - int _current_iterator_pos; - - int populate_thread_group_hierarchy(const JavaThread* jt, Thread* current); - JfrThreadGroupPointers& at(int index); - - public: - JfrThreadGroupsHelper(const JavaThread* jt, Thread* current); - ~JfrThreadGroupsHelper(); - JfrThreadGroupPointers& next(); - bool is_valid() const; - bool has_next() const; -}; - -JfrThreadGroupsHelper::JfrThreadGroupsHelper(const JavaThread* jt, Thread* current) { - _thread_group_hierarchy = new GrowableArray(10); - _current_iterator_pos = populate_thread_group_hierarchy(jt, current) - 1; -} - -JfrThreadGroupsHelper::~JfrThreadGroupsHelper() { - assert(_current_iterator_pos == invalid_iterator_pos, "invariant"); - for (int i = 0; i < _thread_group_hierarchy->length(); ++i) { - _thread_group_hierarchy->at(i)->clear_weak_ref(); - } -} - -JfrThreadGroupPointers& JfrThreadGroupsHelper::at(int index) { - assert(_thread_group_hierarchy != nullptr, "invariant"); - assert(index > invalid_iterator_pos && index < _thread_group_hierarchy->length(), "invariant"); - return *(_thread_group_hierarchy->at(index)); -} - -bool JfrThreadGroupsHelper::has_next() const { - return _current_iterator_pos > invalid_iterator_pos; -} - -bool JfrThreadGroupsHelper::is_valid() const { - return (_thread_group_hierarchy != nullptr && _thread_group_hierarchy->length() > 0); -} - -JfrThreadGroupPointers& JfrThreadGroupsHelper::next() { - assert(is_valid(), "invariant"); - return at(_current_iterator_pos--); -} - -/* - * If not at a safepoint, we create global weak references for - * all reachable threadgroups for this thread. - * If we are at a safepoint, the caller is the VMThread during - * JFR checkpointing. It can use naked oops, because nothing - * will move before the list of threadgroups is cleared and - * mutator threads restarted. The threadgroup list is cleared - * later by the VMThread as one of the final steps in JFR checkpointing - * (not here). - */ -int JfrThreadGroupsHelper::populate_thread_group_hierarchy(const JavaThread* jt, Thread* current) { - assert(jt != nullptr && jt->is_Java_thread(), "invariant"); - assert(current != nullptr, "invariant"); - assert(_thread_group_hierarchy != nullptr, "invariant"); - - oop thread_oop = jt->threadObj(); - if (thread_oop == nullptr) { - return 0; - } - // immediate thread group - Handle thread_group_handle(current, java_lang_Thread::threadGroup(thread_oop)); - if (thread_group_handle == nullptr) { - return 0; - } - - const bool use_weak_handles = !SafepointSynchronize::is_at_safepoint(); - jweak thread_group_weak_ref = use_weak_handles ? JNIHandles::make_weak_global(thread_group_handle) : nullptr; - - JfrThreadGroupPointers* thread_group_pointers = new JfrThreadGroupPointers(thread_group_handle, thread_group_weak_ref); - _thread_group_hierarchy->append(thread_group_pointers); - // immediate parent thread group - oop parent_thread_group_obj = java_lang_ThreadGroup::parent(thread_group_handle()); - Handle parent_thread_group_handle(current, parent_thread_group_obj); - - // and check parents parents... - while (parent_thread_group_handle != nullptr) { - const jweak parent_group_weak_ref = use_weak_handles ? JNIHandles::make_weak_global(parent_thread_group_handle) : nullptr; - thread_group_pointers = new JfrThreadGroupPointers(parent_thread_group_handle, parent_group_weak_ref); - _thread_group_hierarchy->append(thread_group_pointers); - parent_thread_group_obj = java_lang_ThreadGroup::parent(parent_thread_group_handle()); - parent_thread_group_handle = Handle(current, parent_thread_group_obj); - } - return _thread_group_hierarchy->length(); -} - -static traceid next_id() { - static traceid _current_threadgroup_id = 1; // 1 is reserved for thread group "VirtualThreads" - return ++_current_threadgroup_id; -} - -class JfrThreadGroup::JfrThreadGroupEntry : public JfrCHeapObj { - friend class JfrThreadGroup; - private: - traceid _thread_group_id; - traceid _parent_group_id; - char* _thread_group_name; // utf8 format - // If an entry is created during a safepoint, the - // _thread_group_oop contains a direct oop to - // the java.lang.ThreadGroup object. - // If an entry is created on javathread exit time (not at safepoint), - // _thread_group_weak_ref contains a JNI weak global handle - // indirection to the java.lang.ThreadGroup object. - // Note: we cannot use a union here since CHECK_UNHANDLED_OOPS makes oop have - // a ctor which isn't allowed in a union by the SunStudio compiler - oop _thread_group_oop; - jweak _thread_group_weak_ref; - - JfrThreadGroupEntry(const char* tgstr, JfrThreadGroupPointers& ptrs); - ~JfrThreadGroupEntry(); - - traceid thread_group_id() const { return _thread_group_id; } - void set_thread_group_id(traceid tgid) { _thread_group_id = tgid; } - - const char* thread_group_name() const { return _thread_group_name; } - void set_thread_group_name(const char* tgname); - - traceid parent_group_id() const { return _parent_group_id; } - void set_parent_group_id(traceid pgid) { _parent_group_id = pgid; } - - void set_thread_group(JfrThreadGroupPointers& ptrs); - bool is_equal(const JfrThreadGroupPointers& ptrs) const; - oop thread_group() const; -}; - -JfrThreadGroup::JfrThreadGroupEntry::JfrThreadGroupEntry(const char* tgname, JfrThreadGroupPointers& ptrs) : - _thread_group_id(0), - _parent_group_id(0), - _thread_group_name(nullptr), - _thread_group_oop(nullptr), - _thread_group_weak_ref(nullptr) { - set_thread_group_name(tgname); - set_thread_group(ptrs); -} - -JfrThreadGroup::JfrThreadGroupEntry::~JfrThreadGroupEntry() { - if (_thread_group_name != nullptr) { - JfrCHeapObj::free(_thread_group_name, strlen(_thread_group_name) + 1); - } - if (_thread_group_weak_ref != nullptr) { - JNIHandles::destroy_weak_global(_thread_group_weak_ref); - } -} - -void JfrThreadGroup::JfrThreadGroupEntry::set_thread_group_name(const char* tgname) { - assert(_thread_group_name == nullptr, "invariant"); - if (tgname != nullptr) { - size_t len = strlen(tgname); - _thread_group_name = JfrCHeapObj::new_array(len + 1); - strncpy(_thread_group_name, tgname, len + 1); - } -} - -oop JfrThreadGroup::JfrThreadGroupEntry::thread_group() const { - return _thread_group_weak_ref != nullptr ? JNIHandles::resolve(_thread_group_weak_ref) : _thread_group_oop; -} - -void JfrThreadGroup::JfrThreadGroupEntry::set_thread_group(JfrThreadGroupPointers& ptrs) { - _thread_group_weak_ref = ptrs.transfer_weak_global_handle_ownership(); - if (_thread_group_weak_ref == nullptr) { - _thread_group_oop = ptrs.thread_group_oop(); - assert(_thread_group_oop != nullptr, "invariant"); - } else { - _thread_group_oop = nullptr; - } -} - -JfrThreadGroup::JfrThreadGroup() : - _list(new (mtTracing) GrowableArray(initial_array_size, mtTracing)) {} - -JfrThreadGroup::~JfrThreadGroup() { - if (_list != nullptr) { - for (int i = 0; i < _list->length(); i++) { - JfrThreadGroupEntry* e = _list->at(i); - delete e; - } - delete _list; - } -} - -JfrThreadGroup* JfrThreadGroup::instance() { - return _instance; -} - -void JfrThreadGroup::set_instance(JfrThreadGroup* new_instance) { - _instance = new_instance; -} - -traceid JfrThreadGroup::thread_group_id(const JavaThread* jt, Thread* current) { - HandleMark hm(current); - JfrThreadGroupsHelper helper(jt, current); - return helper.is_valid() ? thread_group_id_internal(helper) : 0; -} - -traceid JfrThreadGroup::thread_group_id(JavaThread* const jt) { - return thread_group_id(jt, jt); -} - -traceid JfrThreadGroup::thread_group_id_internal(JfrThreadGroupsHelper& helper) { - ThreadGroupExclusiveAccess lock; - JfrThreadGroup* tg_instance = instance(); - if (tg_instance == nullptr) { - tg_instance = new JfrThreadGroup(); - if (tg_instance == nullptr) { - return 0; - } - set_instance(tg_instance); - } - - JfrThreadGroupEntry* tge = nullptr; - traceid parent_thread_group_id = 0; - while (helper.has_next()) { - JfrThreadGroupPointers& ptrs = helper.next(); - tge = tg_instance->find_entry(ptrs); - if (nullptr == tge) { - tge = tg_instance->new_entry(ptrs); - assert(tge != nullptr, "invariant"); - tge->set_parent_group_id(parent_thread_group_id); - } - parent_thread_group_id = tge->thread_group_id(); - } - // the last entry in the hierarchy is the immediate thread group - return tge->thread_group_id(); -} - -bool JfrThreadGroup::JfrThreadGroupEntry::is_equal(const JfrThreadGroupPointers& ptrs) const { - return ptrs.thread_group_oop() == thread_group(); -} - -JfrThreadGroup::JfrThreadGroupEntry* -JfrThreadGroup::find_entry(const JfrThreadGroupPointers& ptrs) const { - for (int index = 0; index < _list->length(); ++index) { - JfrThreadGroupEntry* curtge = _list->at(index); - if (curtge->is_equal(ptrs)) { - return curtge; - } - } - return (JfrThreadGroupEntry*) nullptr; -} - -// Assumes you already searched for the existence -// of a corresponding entry in find_entry(). -JfrThreadGroup::JfrThreadGroupEntry* -JfrThreadGroup::new_entry(JfrThreadGroupPointers& ptrs) { - JfrThreadGroupEntry* const tge = new JfrThreadGroupEntry(java_lang_ThreadGroup::name(ptrs.thread_group_oop()), ptrs); - add_entry(tge); - return tge; -} - -int JfrThreadGroup::add_entry(JfrThreadGroupEntry* tge) { - assert(tge != nullptr, "attempting to add a null entry!"); - assert(0 == tge->thread_group_id(), "id must be unassigned!"); - tge->set_thread_group_id(next_id()); - return _list->append(tge); -} - -void JfrThreadGroup::write_thread_group_entries(JfrCheckpointWriter& writer) const { - assert(_list != nullptr && !_list->is_empty(), "should not need be here!"); - const int number_of_tg_entries = _list->length(); - writer.write_count(number_of_tg_entries + 1); // + VirtualThread group - writer.write_key(1); // 1 is reserved for VirtualThread group - writer.write(0); // parent - const oop vgroup = java_lang_Thread_Constants::get_VTHREAD_GROUP(); - assert(vgroup != (oop)nullptr, "invariant"); - const char* const vgroup_name = java_lang_ThreadGroup::name(vgroup); - assert(vgroup_name != nullptr, "invariant"); - writer.write(vgroup_name); - for (int index = 0; index < number_of_tg_entries; ++index) { - const JfrThreadGroupEntry* const curtge = _list->at(index); - writer.write_key(curtge->thread_group_id()); - writer.write(curtge->parent_group_id()); - writer.write(curtge->thread_group_name()); - } -} - -void JfrThreadGroup::write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const { - assert(writer != nullptr, "invariant"); - assert(_list != nullptr && !_list->is_empty(), "should not need be here!"); - assert(thread_group_id != 1, "should not need be here!"); - const int number_of_tg_entries = _list->length(); - - // save context - const JfrCheckpointContext ctx = writer->context(); - writer->write_type(TYPE_THREADGROUP); - const jlong count_offset = writer->reserve(sizeof(u4)); // Don't know how many yet - int number_of_entries_written = 0; - for (int index = number_of_tg_entries - 1; index >= 0; --index) { - const JfrThreadGroupEntry* const curtge = _list->at(index); - if (thread_group_id == curtge->thread_group_id()) { - writer->write_key(curtge->thread_group_id()); - writer->write(curtge->parent_group_id()); - writer->write(curtge->thread_group_name()); - ++number_of_entries_written; - thread_group_id = curtge->parent_group_id(); - } - } - if (number_of_entries_written == 0) { - // nothing to write, restore context - writer->set_context(ctx); - return; - } - assert(number_of_entries_written > 0, "invariant"); - writer->write_count(number_of_entries_written, count_offset); -} - -// Write out JfrThreadGroup instance and then delete it -void JfrThreadGroup::serialize(JfrCheckpointWriter& writer) { - ThreadGroupExclusiveAccess lock; - JfrThreadGroup* tg_instance = instance(); - assert(tg_instance != nullptr, "invariant"); - tg_instance->write_thread_group_entries(writer); -} - -// for writing a particular thread group -void JfrThreadGroup::serialize(JfrCheckpointWriter* writer, traceid thread_group_id) { - assert(writer != nullptr, "invariant"); - ThreadGroupExclusiveAccess lock; - JfrThreadGroup* const tg_instance = instance(); - assert(tg_instance != nullptr, "invariant"); - tg_instance->write_selective_thread_group(writer, thread_group_id); -} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp new file mode 100644 index 00000000000..55864af4d5c --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrLinkedList.inline.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/jniHandles.inline.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/semaphore.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/growableArray.hpp" + +class ThreadGroupExclusiveAccess : public StackObj { + private: + static Semaphore _mutex_semaphore; + public: + ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); } + ~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); } +}; + +Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1); + +static traceid next_id() { + static traceid _tgid = 1; // 1 is reserved for thread group "VirtualThreads" + return ++_tgid; +} + +class JfrThreadGroup : public JfrCHeapObj { + template + friend class JfrLinkedList; + private: + mutable const JfrThreadGroup* _next; + const JfrThreadGroup* _parent; + traceid _tgid; + char* _tg_name; // utf8 format + jweak _tg_handle; + mutable u2 _generation; + + public: + JfrThreadGroup(Handle tg, const JfrThreadGroup* parent) : + _next(nullptr), _parent(parent), _tgid(next_id()), _tg_name(nullptr), + _tg_handle(JNIHandles::make_weak_global(tg)), _generation(0) { + const char* name = java_lang_ThreadGroup::name(tg()); + if (name != nullptr) { + const size_t len = strlen(name); + _tg_name = JfrCHeapObj::new_array(len + 1); + strncpy(_tg_name, name, len + 1); + } + } + + ~JfrThreadGroup() { + JNIHandles::destroy_weak_global(_tg_handle); + if (_tg_name != nullptr) { + JfrCHeapObj::free(_tg_name, strlen(_tg_name) + 1); + } + } + + const JfrThreadGroup* next() const { return _next; } + + traceid id() const { return _tgid; } + + const char* name() const { + return _tg_name; + } + + const JfrThreadGroup* parent() const { return _parent; } + + traceid parent_id() const { + return _parent != nullptr ? _parent->id() : 0; + } + + bool is_dead() const { + return JNIHandles::resolve(_tg_handle) == nullptr; + } + + bool operator==(oop tg) const { + assert(tg != nullptr, "invariant"); + return tg == JNIHandles::resolve(_tg_handle); + } + + bool should_write() const { + return !JfrTraceIdEpoch::is_current_epoch_generation(_generation); + } + + void set_written() const { + assert(should_write(), "invariant"); + _generation = JfrTraceIdEpoch::epoch_generation(); + } +}; + +typedef JfrLinkedList JfrThreadGroupList; + +static JfrThreadGroupList* _list = nullptr; + +static JfrThreadGroupList& list() { + assert(_list != nullptr, "invariant"); + return *_list; +} + +bool JfrThreadGroupManager::create() { + assert(_list == nullptr, "invariant"); + _list = new JfrThreadGroupList(); + return _list != nullptr; +} + +void JfrThreadGroupManager::destroy() { + delete _list; + _list = nullptr; +} + +static int populate(GrowableArray* hierarchy, const JavaThread* jt, Thread* current) { + assert(hierarchy != nullptr, "invariant"); + assert(jt != nullptr, "invariant"); + assert(current == Thread::current(), "invariant"); + + oop thread_oop = jt->threadObj(); + if (thread_oop == nullptr) { + return 0; + } + // Immediate thread group. + const Handle tg_handle(current, java_lang_Thread::threadGroup(thread_oop)); + if (tg_handle.is_null()) { + return 0; + } + hierarchy->append(tg_handle); + + // Thread group parent and then its parents... + Handle parent_tg_handle(current, java_lang_ThreadGroup::parent(tg_handle())); + + while (parent_tg_handle != nullptr) { + hierarchy->append(parent_tg_handle); + parent_tg_handle = Handle(current, java_lang_ThreadGroup::parent(parent_tg_handle())); + } + + return hierarchy->length(); +} + +class JfrThreadGroupLookup : public ResourceObj { + static const int invalid_iterator = -1; + private: + GrowableArray* _hierarchy; + mutable int _iterator; + + public: + JfrThreadGroupLookup(const JavaThread* jt, Thread* current) : + _hierarchy(new GrowableArray(16)), + _iterator(populate(_hierarchy, jt, current) - 1) {} + + bool has_next() const { + return _iterator > invalid_iterator; + } + + const Handle& next() const { + assert(has_next(), "invariant"); + return _hierarchy->at(_iterator--); + } +}; + +static const JfrThreadGroup* find_or_add(const Handle& tg_oop, const JfrThreadGroup* parent) { + assert(parent == nullptr || list().in_list(parent), "invariant"); + const JfrThreadGroup* tg = list().head(); + const JfrThreadGroup* result = nullptr; + while (tg != nullptr) { + if (*tg == tg_oop()) { + assert(tg->parent() == parent, "invariant"); + result = tg; + tg = nullptr; + continue; + } + tg = tg->next(); + } + if (result == nullptr) { + result = new JfrThreadGroup(tg_oop, parent); + list().add(result); + } + return result; +} + +static traceid find_tgid(const JfrThreadGroupLookup& lookup) { + const JfrThreadGroup* tg = nullptr; + const JfrThreadGroup* ptg = nullptr; + while (lookup.has_next()) { + tg = find_or_add(lookup.next(), ptg); + ptg = tg; + } + return tg != nullptr ? tg->id() : 0; +} + +static traceid find(const JfrThreadGroupLookup& lookup) { + ThreadGroupExclusiveAccess lock; + return find_tgid(lookup); +} + +traceid JfrThreadGroupManager::thread_group_id(JavaThread* jt) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);) + ResourceMark rm(jt); + HandleMark hm(jt); + const JfrThreadGroupLookup lookup(jt, jt); + return find(lookup); +} + +traceid JfrThreadGroupManager::thread_group_id(const JavaThread* jt, Thread* current) { + assert(jt != nullptr, "invariant"); + assert(current != nullptr, "invariant"); + assert(!current->is_Java_thread() || JavaThread::cast(current)->thread_state() == _thread_in_vm, "invariant"); + ResourceMark rm(current); + HandleMark hm(current); + const JfrThreadGroupLookup lookup(jt, current); + return find(lookup); +} + +static void write_virtual_thread_group(JfrCheckpointWriter& writer) { + writer.write_key(1); // 1 is reserved for VirtualThread group + writer.write(0); // parent + const oop vgroup = java_lang_Thread_Constants::get_VTHREAD_GROUP(); + assert(vgroup != (oop)nullptr, "invariant"); + const char* const vgroup_name = java_lang_ThreadGroup::name(vgroup); + assert(vgroup_name != nullptr, "invariant"); + writer.write(vgroup_name); +} + +static int write_thread_group(JfrCheckpointWriter& writer, const JfrThreadGroup* tg, bool to_blob = false) { + assert(tg != nullptr, "invariant"); + if (tg->should_write() || to_blob) { + writer.write_key(tg->id()); + writer.write(tg->parent_id()); + writer.write(tg->name()); + if (!to_blob) { + tg->set_written(); + } + return 1; + } + return 0; +} + +// For writing all live thread groups while removing and deleting dead thread groups. +void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());) + + const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet + + // First write the pre-defined ThreadGroup for virtual threads. + write_virtual_thread_group(writer); + int number_of_groups_written = 1; + + const JfrThreadGroup* next = nullptr; + const JfrThreadGroup* prev = nullptr; + + { + ThreadGroupExclusiveAccess lock; + const JfrThreadGroup* tg = list().head(); + while (tg != nullptr) { + next = tg->next(); + if (tg->is_dead()) { + prev = list().excise(prev, tg); + assert(!list().in_list(tg), "invariant"); + delete tg; + tg = next; + continue; + } + number_of_groups_written += write_thread_group(writer, tg); + prev = tg; + tg = next; + } + } + + assert(number_of_groups_written > 0, "invariant"); + writer.write_count(number_of_groups_written, count_offset); +} + +// For writing a specific thread group and its ancestry. +void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer, traceid tgid, bool to_blob) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());) + // save context + const JfrCheckpointContext ctx = writer.context(); + + writer.write_type(TYPE_THREADGROUP); + const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet + + int number_of_groups_written = 0; + + { + ThreadGroupExclusiveAccess lock; + const JfrThreadGroup* tg = list().head(); + while (tg != nullptr) { + if (tgid == tg->id()) { + while (tg != nullptr) { + number_of_groups_written += write_thread_group(writer, tg, to_blob); + tg = tg->parent(); + } + break; + } + tg = tg->next(); + } + } + + if (number_of_groups_written == 0) { + // nothing to write, restore context + writer.set_context(ctx); + return; + } + + assert(number_of_groups_written > 0, "invariant"); + writer.write_count(number_of_groups_written, count_offset); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp similarity index 50% rename from src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp rename to src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp index 8226c6ebef2..22c140c6f8f 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,44 +22,27 @@ * */ -#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP -#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP +#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP +#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP -#include "jfr/utilities/jfrAllocation.hpp" #include "jfr/utilities/jfrTypes.hpp" -#include "jni.h" +#include "memory/allStatic.hpp" class JfrCheckpointWriter; -template -class GrowableArray; -class JfrThreadGroupsHelper; -class JfrThreadGroupPointers; -class JfrThreadGroup : public JfrCHeapObj { - friend class JfrCheckpointThreadClosure; +class JfrThreadGroupManager : public AllStatic { + friend class JfrRecorder; + private: - static JfrThreadGroup* _instance; - class JfrThreadGroupEntry; - GrowableArray* _list; - - JfrThreadGroup(); - JfrThreadGroupEntry* find_entry(const JfrThreadGroupPointers& ptrs) const; - JfrThreadGroupEntry* new_entry(JfrThreadGroupPointers& ptrs); - int add_entry(JfrThreadGroupEntry* const tge); - - void write_thread_group_entries(JfrCheckpointWriter& writer) const; - void write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const; - - static traceid thread_group_id_internal(JfrThreadGroupsHelper& helper); - static JfrThreadGroup* instance(); - static void set_instance(JfrThreadGroup* new_instance); + static bool create(); + static void destroy(); public: - ~JfrThreadGroup(); static void serialize(JfrCheckpointWriter& w); - static void serialize(JfrCheckpointWriter* w, traceid thread_group_id); + static void serialize(JfrCheckpointWriter& w, traceid tgid, bool is_blob); + static traceid thread_group_id(JavaThread* thread); static traceid thread_group_id(const JavaThread* thread, Thread* current); }; -#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP +#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp index 9179395a451..17d945af65e 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp @@ -32,7 +32,7 @@ #include "gc/shared/gcWhen.hpp" #include "jfr/leakprofiler/leakProfiler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" -#include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp" #include "jfr/recorder/checkpoint/types/jfrThreadState.hpp" #include "jfr/recorder/checkpoint/types/jfrType.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -106,7 +106,7 @@ void JfrCheckpointThreadClosure::do_thread(Thread* t) { } else { _writer.write(name); _writer.write(tid); - _writer.write(JfrThreadGroup::thread_group_id(JavaThread::cast(t), _curthread)); + _writer.write(JfrThreadGroupManager::thread_group_id(JavaThread::cast(t), _curthread)); } _writer.write(false); // isVirtual } @@ -115,7 +115,10 @@ void JfrThreadConstantSet::serialize(JfrCheckpointWriter& writer) { JfrCheckpointThreadClosure tc(writer); JfrJavaThreadIterator javathreads; while (javathreads.has_next()) { - tc.do_thread(javathreads.next()); + JavaThread* const jt = javathreads.next(); + if (jt->jfr_thread_local()->should_write()) { + tc.do_thread(jt); + } } JfrNonJavaThreadIterator nonjavathreads; while (nonjavathreads.has_next()) { @@ -124,7 +127,7 @@ void JfrThreadConstantSet::serialize(JfrCheckpointWriter& writer) { } void JfrThreadGroupConstant::serialize(JfrCheckpointWriter& writer) { - JfrThreadGroup::serialize(writer); + JfrThreadGroupManager::serialize(writer); } static const char* flag_value_origin_to_string(JVMFlagOrigin origin) { @@ -303,11 +306,11 @@ void JfrThreadConstant::serialize(JfrCheckpointWriter& writer) { writer.write(JfrThreadId::jfr_id(_thread, _tid)); // java thread group - VirtualThread threadgroup reserved id 1 const traceid thread_group_id = is_vthread ? 1 : - JfrThreadGroup::thread_group_id(JavaThread::cast(_thread), Thread::current()); + JfrThreadGroupManager::thread_group_id(JavaThread::cast(_thread), Thread::current()); writer.write(thread_group_id); writer.write(is_vthread); // isVirtual - if (!is_vthread) { - JfrThreadGroup::serialize(&writer, thread_group_id); + if (thread_group_id > 1) { + JfrThreadGroupManager::serialize(writer, thread_group_id, _to_blob); } // VirtualThread threadgroup already serialized invariant. } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp index de35ecaf917..9798a6d29c0 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -109,11 +109,12 @@ class JfrThreadConstant : public JfrSerializer { oop _vthread; const char* _name; int _length; + const bool _to_blob; void write_name(JfrCheckpointWriter& writer); void write_os_name(JfrCheckpointWriter& writer, bool is_vthread); public: - JfrThreadConstant(Thread* t, traceid tid, oop vthread = nullptr) : - _thread(t), _tid(tid), _vthread(vthread), _name(nullptr), _length(-1) {} + JfrThreadConstant(Thread* t, traceid tid, bool to_blob, oop vthread = nullptr) : + _thread(t), _tid(tid), _vthread(vthread), _name(nullptr), _length(-1), _to_blob(to_blob) {} void serialize(JfrCheckpointWriter& writer); }; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp index 493942bade4..58d12f70980 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp @@ -109,7 +109,7 @@ JfrBlobHandle JfrTypeManager::create_thread_blob(JavaThread* jt, traceid tid /* // TYPE_THREAD and count is written unconditionally for blobs, also for vthreads. writer.write_type(TYPE_THREAD); writer.write_count(1); - JfrThreadConstant type_thread(jt, tid, vthread); + JfrThreadConstant type_thread(jt, tid, true, vthread); type_thread.serialize(writer); return writer.move(); } @@ -128,7 +128,7 @@ void JfrTypeManager::write_checkpoint(Thread* t, traceid tid /* 0 */, oop vthrea writer.write_type(TYPE_THREAD); writer.write_count(1); } - JfrThreadConstant type_thread(t, tid, vthread); + JfrThreadConstant type_thread(t, tid, false, vthread); type_thread.serialize(writer); } diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index dd75cb2929f..4ef278ab522 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -33,6 +33,7 @@ #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp" #include "jfr/recorder/repository/jfrRepository.hpp" #include "jfr/recorder/service/jfrEventThrottler.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" @@ -311,6 +312,9 @@ bool JfrRecorder::create_components() { if (!create_event_throttler()) { return false; } + if (!create_thread_group_manager()) { + return false; + } return true; } @@ -405,6 +409,10 @@ bool JfrRecorder::create_event_throttler() { return JfrEventThrottler::create(); } +bool JfrRecorder::create_thread_group_manager() { + return JfrThreadGroupManager::create(); +} + void JfrRecorder::destroy_components() { JfrJvmtiAgent::destroy(); if (_post_box != nullptr) { @@ -444,6 +452,7 @@ void JfrRecorder::destroy_components() { _cpu_time_thread_sampling = nullptr; } JfrEventThrottler::destroy(); + JfrThreadGroupManager::destroy(); } bool JfrRecorder::create_recorder_thread() { diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp index 3099c8ad344..34cc8fda949 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp @@ -53,6 +53,7 @@ class JfrRecorder : public JfrCHeapObj { static bool create_stacktrace_repository(); static bool create_storage(); static bool create_stringpool(); + static bool create_thread_group_manager(); static bool create_thread_sampler(); static bool create_cpu_time_thread_sampling(); static bool create_event_throttler(); diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp index e38e0427a05..291169b9aa7 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp @@ -29,6 +29,7 @@ #include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" @@ -75,6 +76,7 @@ JfrThreadLocal::JfrThreadLocal() : _wallclock_time(os::javaTimeNanos()), _non_reentrant_nesting(0), _vthread_epoch(0), + _generation(0), _vthread_excluded(false), _jvm_thread_excluded(false), _enqueued_requests(false), @@ -136,17 +138,33 @@ static void send_java_thread_start_event(JavaThread* jt) { } void JfrThreadLocal::on_start(Thread* t) { - assign_thread_id(t, t->jfr_thread_local()); + JfrThreadLocal* const tl = t->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + assign_thread_id(t, tl); if (JfrRecorder::is_recording()) { - JfrCheckpointManager::write_checkpoint(t); - if (t->is_Java_thread()) { - JavaThread *const jt = JavaThread::cast(t); + if (!t->is_Java_thread()) { + JfrCheckpointManager::write_checkpoint(t); + return; + } + JavaThread* const jt = JavaThread::cast(t); + if (jt->thread_state() == _thread_new) { JfrCPUTimeThreadSampling::on_javathread_create(jt); + } else { + assert(jt->thread_state() == _thread_in_vm, "invariant"); + if (tl->should_write()) { + JfrCheckpointManager::write_checkpoint(t); + } send_java_thread_start_event(jt); + if (tl->has_cached_stack_trace()) { + tl->clear_cached_stack_trace(); + } + return; } } - if (t->jfr_thread_local()->has_cached_stack_trace()) { - t->jfr_thread_local()->clear_cached_stack_trace(); + if (t->is_Java_thread() && JavaThread::cast(t)->thread_state() == _thread_in_vm) { + if (tl->has_cached_stack_trace()) { + tl->clear_cached_stack_trace(); + } } } @@ -229,13 +247,18 @@ void JfrThreadLocal::on_exit(Thread* t) { JfrThreadLocal * const tl = t->jfr_thread_local(); assert(!tl->is_dead(), "invariant"); if (JfrRecorder::is_recording()) { - JfrCheckpointManager::write_checkpoint(t); - } - if (t->is_Java_thread()) { - JavaThread* const jt = JavaThread::cast(t); - send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); - JfrCPUTimeThreadSampling::on_javathread_terminate(jt); - JfrThreadCPULoadEvent::send_event_for_thread(jt); + if (!t->is_Java_thread()) { + JfrCheckpointManager::write_checkpoint(t); + } else { + JavaThread* const jt = JavaThread::cast(t); + assert(jt->thread_state() == _thread_in_vm, "invariant"); + if (tl->should_write()) { + JfrCheckpointManager::write_checkpoint(t); + } + send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); + JfrCPUTimeThreadSampling::on_javathread_terminate(jt); + JfrThreadCPULoadEvent::send_event_for_thread(jt); + } } release(tl, Thread::current()); // because it could be that Thread::current() != t } @@ -425,6 +448,15 @@ u2 JfrThreadLocal::vthread_epoch(const JavaThread* jt) { return Atomic::load(&jt->jfr_thread_local()->_vthread_epoch); } +bool JfrThreadLocal::should_write() const { + const u2 current_generation = JfrTraceIdEpoch::epoch_generation(); + if (Atomic::load(&_generation) != current_generation) { + Atomic::store(&_generation, current_generation); + return true; + } + return false; +} + traceid JfrThreadLocal::thread_id(const Thread* t) { assert(t != nullptr, "invariant"); if (is_impersonating(t)) { diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 715a2c44f93..8c82dfad8af 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -75,6 +75,7 @@ class JfrThreadLocal { jlong _wallclock_time; int32_t _non_reentrant_nesting; u2 _vthread_epoch; + mutable u2 _generation; bool _vthread_excluded; bool _jvm_thread_excluded; volatile bool _enqueued_requests; @@ -348,6 +349,9 @@ class JfrThreadLocal { return _sampling_critical_section; } + // Serialization state. + bool should_write() const; + static int32_t make_non_reentrant(Thread* thread); static void make_reentrant(Thread* thread, int32_t previous_nesting); diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 3d9421fc94c..a0ac7bd4768 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -738,6 +738,8 @@ void JavaThread::run() { assert(JavaThread::current() == this, "sanity check"); assert(!Thread::current()->owns_locks(), "sanity check"); + JFR_ONLY(Jfr::on_thread_start(this);) + DTRACE_THREAD_PROBE(start, this); // This operation might block. We call that after all safepoint checks for a new thread has