Skip to content

Commit 1e2bf07

Browse files
author
Markus Grönlund
committed
8364258: ThreadGroup constant pool serialization is not normalized
Reviewed-by: egahlin Backport-of: 3bc449797eb59f9770d2a06d260b23b6efd5ff0f
1 parent 24936b9 commit 1e2bf07

File tree

11 files changed

+420
-471
lines changed

11 files changed

+420
-471
lines changed

src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp

Lines changed: 0 additions & 417 deletions
This file was deleted.
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/*
2+
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*
23+
*/
24+
25+
#include "jfr/jni/jfrJavaSupport.hpp"
26+
#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
27+
#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp"
28+
#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp"
29+
#include "jfr/utilities/jfrAllocation.hpp"
30+
#include "jfr/utilities/jfrLinkedList.inline.hpp"
31+
#include "memory/resourceArea.hpp"
32+
#include "runtime/handles.inline.hpp"
33+
#include "runtime/jniHandles.inline.hpp"
34+
#include "runtime/safepoint.hpp"
35+
#include "runtime/semaphore.hpp"
36+
#include "runtime/thread.inline.hpp"
37+
#include "utilities/growableArray.hpp"
38+
39+
class ThreadGroupExclusiveAccess : public StackObj {
40+
private:
41+
static Semaphore _mutex_semaphore;
42+
public:
43+
ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); }
44+
~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); }
45+
};
46+
47+
Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1);
48+
49+
static traceid next_id() {
50+
static traceid _tgid = 1; // 1 is reserved for thread group "VirtualThreads"
51+
return ++_tgid;
52+
}
53+
54+
class JfrThreadGroup : public JfrCHeapObj {
55+
template <typename, typename>
56+
friend class JfrLinkedList;
57+
private:
58+
mutable const JfrThreadGroup* _next;
59+
const JfrThreadGroup* _parent;
60+
traceid _tgid;
61+
char* _tg_name; // utf8 format
62+
jweak _tg_handle;
63+
mutable u2 _generation;
64+
65+
public:
66+
JfrThreadGroup(Handle tg, const JfrThreadGroup* parent) :
67+
_next(nullptr), _parent(parent), _tgid(next_id()), _tg_name(nullptr),
68+
_tg_handle(JNIHandles::make_weak_global(tg)), _generation(0) {
69+
const char* name = java_lang_ThreadGroup::name(tg());
70+
if (name != nullptr) {
71+
const size_t len = strlen(name);
72+
_tg_name = JfrCHeapObj::new_array<char>(len + 1);
73+
strncpy(_tg_name, name, len + 1);
74+
}
75+
}
76+
77+
~JfrThreadGroup() {
78+
JNIHandles::destroy_weak_global(_tg_handle);
79+
if (_tg_name != nullptr) {
80+
JfrCHeapObj::free(_tg_name, strlen(_tg_name) + 1);
81+
}
82+
}
83+
84+
const JfrThreadGroup* next() const { return _next; }
85+
86+
traceid id() const { return _tgid; }
87+
88+
const char* name() const {
89+
return _tg_name;
90+
}
91+
92+
const JfrThreadGroup* parent() const { return _parent; }
93+
94+
traceid parent_id() const {
95+
return _parent != nullptr ? _parent->id() : 0;
96+
}
97+
98+
bool is_dead() const {
99+
return JNIHandles::resolve(_tg_handle) == nullptr;
100+
}
101+
102+
bool operator==(oop tg) const {
103+
assert(tg != nullptr, "invariant");
104+
return tg == JNIHandles::resolve(_tg_handle);
105+
}
106+
107+
bool should_write() const {
108+
return !JfrTraceIdEpoch::is_current_epoch_generation(_generation);
109+
}
110+
111+
void set_written() const {
112+
assert(should_write(), "invariant");
113+
_generation = JfrTraceIdEpoch::epoch_generation();
114+
}
115+
};
116+
117+
typedef JfrLinkedList<const JfrThreadGroup> JfrThreadGroupList;
118+
119+
static JfrThreadGroupList* _list = nullptr;
120+
121+
static JfrThreadGroupList& list() {
122+
assert(_list != nullptr, "invariant");
123+
return *_list;
124+
}
125+
126+
bool JfrThreadGroupManager::create() {
127+
assert(_list == nullptr, "invariant");
128+
_list = new JfrThreadGroupList();
129+
return _list != nullptr;
130+
}
131+
132+
void JfrThreadGroupManager::destroy() {
133+
delete _list;
134+
_list = nullptr;
135+
}
136+
137+
static int populate(GrowableArray<Handle>* hierarchy, const JavaThread* jt, Thread* current) {
138+
assert(hierarchy != nullptr, "invariant");
139+
assert(jt != nullptr, "invariant");
140+
assert(current == Thread::current(), "invariant");
141+
142+
oop thread_oop = jt->threadObj();
143+
if (thread_oop == nullptr) {
144+
return 0;
145+
}
146+
// Immediate thread group.
147+
const Handle tg_handle(current, java_lang_Thread::threadGroup(thread_oop));
148+
if (tg_handle.is_null()) {
149+
return 0;
150+
}
151+
hierarchy->append(tg_handle);
152+
153+
// Thread group parent and then its parents...
154+
Handle parent_tg_handle(current, java_lang_ThreadGroup::parent(tg_handle()));
155+
156+
while (parent_tg_handle != nullptr) {
157+
hierarchy->append(parent_tg_handle);
158+
parent_tg_handle = Handle(current, java_lang_ThreadGroup::parent(parent_tg_handle()));
159+
}
160+
161+
return hierarchy->length();
162+
}
163+
164+
class JfrThreadGroupLookup : public ResourceObj {
165+
static const int invalid_iterator = -1;
166+
private:
167+
GrowableArray<Handle>* _hierarchy;
168+
mutable int _iterator;
169+
170+
public:
171+
JfrThreadGroupLookup(const JavaThread* jt, Thread* current) :
172+
_hierarchy(new GrowableArray<Handle>(16)),
173+
_iterator(populate(_hierarchy, jt, current) - 1) {}
174+
175+
bool has_next() const {
176+
return _iterator > invalid_iterator;
177+
}
178+
179+
const Handle& next() const {
180+
assert(has_next(), "invariant");
181+
return _hierarchy->at(_iterator--);
182+
}
183+
};
184+
185+
static const JfrThreadGroup* find_or_add(const Handle& tg_oop, const JfrThreadGroup* parent) {
186+
assert(parent == nullptr || list().in_list(parent), "invariant");
187+
const JfrThreadGroup* tg = list().head();
188+
const JfrThreadGroup* result = nullptr;
189+
while (tg != nullptr) {
190+
if (*tg == tg_oop()) {
191+
assert(tg->parent() == parent, "invariant");
192+
result = tg;
193+
tg = nullptr;
194+
continue;
195+
}
196+
tg = tg->next();
197+
}
198+
if (result == nullptr) {
199+
result = new JfrThreadGroup(tg_oop, parent);
200+
list().add(result);
201+
}
202+
return result;
203+
}
204+
205+
static traceid find_tgid(const JfrThreadGroupLookup& lookup) {
206+
const JfrThreadGroup* tg = nullptr;
207+
const JfrThreadGroup* ptg = nullptr;
208+
while (lookup.has_next()) {
209+
tg = find_or_add(lookup.next(), ptg);
210+
ptg = tg;
211+
}
212+
return tg != nullptr ? tg->id() : 0;
213+
}
214+
215+
static traceid find(const JfrThreadGroupLookup& lookup) {
216+
ThreadGroupExclusiveAccess lock;
217+
return find_tgid(lookup);
218+
}
219+
220+
traceid JfrThreadGroupManager::thread_group_id(JavaThread* jt) {
221+
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);)
222+
ResourceMark rm(jt);
223+
HandleMark hm(jt);
224+
const JfrThreadGroupLookup lookup(jt, jt);
225+
return find(lookup);
226+
}
227+
228+
traceid JfrThreadGroupManager::thread_group_id(const JavaThread* jt, Thread* current) {
229+
assert(jt != nullptr, "invariant");
230+
assert(current != nullptr, "invariant");
231+
assert(!current->is_Java_thread() || JavaThread::cast(current)->thread_state() == _thread_in_vm, "invariant");
232+
ResourceMark rm(current);
233+
HandleMark hm(current);
234+
const JfrThreadGroupLookup lookup(jt, current);
235+
return find(lookup);
236+
}
237+
238+
static void write_virtual_thread_group(JfrCheckpointWriter& writer) {
239+
writer.write_key(1); // 1 is reserved for VirtualThread group
240+
writer.write<traceid>(0); // parent
241+
const oop vgroup = java_lang_Thread_Constants::get_VTHREAD_GROUP();
242+
assert(vgroup != (oop)nullptr, "invariant");
243+
const char* const vgroup_name = java_lang_ThreadGroup::name(vgroup);
244+
assert(vgroup_name != nullptr, "invariant");
245+
writer.write(vgroup_name);
246+
}
247+
248+
static int write_thread_group(JfrCheckpointWriter& writer, const JfrThreadGroup* tg, bool to_blob = false) {
249+
assert(tg != nullptr, "invariant");
250+
if (tg->should_write() || to_blob) {
251+
writer.write_key(tg->id());
252+
writer.write(tg->parent_id());
253+
writer.write(tg->name());
254+
if (!to_blob) {
255+
tg->set_written();
256+
}
257+
return 1;
258+
}
259+
return 0;
260+
}
261+
262+
// For writing all live thread groups while removing and deleting dead thread groups.
263+
void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer) {
264+
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());)
265+
266+
const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet
267+
268+
// First write the pre-defined ThreadGroup for virtual threads.
269+
write_virtual_thread_group(writer);
270+
int number_of_groups_written = 1;
271+
272+
const JfrThreadGroup* next = nullptr;
273+
const JfrThreadGroup* prev = nullptr;
274+
275+
{
276+
ThreadGroupExclusiveAccess lock;
277+
const JfrThreadGroup* tg = list().head();
278+
while (tg != nullptr) {
279+
next = tg->next();
280+
if (tg->is_dead()) {
281+
prev = list().excise(prev, tg);
282+
assert(!list().in_list(tg), "invariant");
283+
delete tg;
284+
tg = next;
285+
continue;
286+
}
287+
number_of_groups_written += write_thread_group(writer, tg);
288+
prev = tg;
289+
tg = next;
290+
}
291+
}
292+
293+
assert(number_of_groups_written > 0, "invariant");
294+
writer.write_count(number_of_groups_written, count_offset);
295+
}
296+
297+
// For writing a specific thread group and its ancestry.
298+
void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer, traceid tgid, bool to_blob) {
299+
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());)
300+
// save context
301+
const JfrCheckpointContext ctx = writer.context();
302+
303+
writer.write_type(TYPE_THREADGROUP);
304+
const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet
305+
306+
int number_of_groups_written = 0;
307+
308+
{
309+
ThreadGroupExclusiveAccess lock;
310+
const JfrThreadGroup* tg = list().head();
311+
while (tg != nullptr) {
312+
if (tgid == tg->id()) {
313+
while (tg != nullptr) {
314+
number_of_groups_written += write_thread_group(writer, tg, to_blob);
315+
tg = tg->parent();
316+
}
317+
break;
318+
}
319+
tg = tg->next();
320+
}
321+
}
322+
323+
if (number_of_groups_written == 0) {
324+
// nothing to write, restore context
325+
writer.set_context(ctx);
326+
return;
327+
}
328+
329+
assert(number_of_groups_written > 0, "invariant");
330+
writer.write_count(number_of_groups_written, count_offset);
331+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -22,44 +22,27 @@
2222
*
2323
*/
2424

25-
#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP
26-
#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP
25+
#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP
26+
#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP
2727

28-
#include "jfr/utilities/jfrAllocation.hpp"
2928
#include "jfr/utilities/jfrTypes.hpp"
30-
#include "jni.h"
29+
#include "memory/allStatic.hpp"
3130

3231
class JfrCheckpointWriter;
33-
template <typename>
34-
class GrowableArray;
35-
class JfrThreadGroupsHelper;
36-
class JfrThreadGroupPointers;
3732

38-
class JfrThreadGroup : public JfrCHeapObj {
39-
friend class JfrCheckpointThreadClosure;
40-
private:
41-
static JfrThreadGroup* _instance;
42-
class JfrThreadGroupEntry;
43-
GrowableArray<JfrThreadGroupEntry*>* _list;
44-
45-
JfrThreadGroup();
46-
JfrThreadGroupEntry* find_entry(const JfrThreadGroupPointers& ptrs) const;
47-
JfrThreadGroupEntry* new_entry(JfrThreadGroupPointers& ptrs);
48-
int add_entry(JfrThreadGroupEntry* const tge);
49-
50-
void write_thread_group_entries(JfrCheckpointWriter& writer) const;
51-
void write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const;
33+
class JfrThreadGroupManager : public AllStatic {
34+
friend class JfrRecorder;
5235

53-
static traceid thread_group_id_internal(JfrThreadGroupsHelper& helper);
54-
static JfrThreadGroup* instance();
55-
static void set_instance(JfrThreadGroup* new_instance);
36+
private:
37+
static bool create();
38+
static void destroy();
5639

5740
public:
58-
~JfrThreadGroup();
5941
static void serialize(JfrCheckpointWriter& w);
60-
static void serialize(JfrCheckpointWriter* w, traceid thread_group_id);
42+
static void serialize(JfrCheckpointWriter& w, traceid tgid, bool is_blob);
43+
6144
static traceid thread_group_id(JavaThread* thread);
6245
static traceid thread_group_id(const JavaThread* thread, Thread* current);
6346
};
6447

65-
#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP
48+
#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP

0 commit comments

Comments
 (0)