Skip to content

Commit 6ccf335

Browse files
author
Daniil Titov
committed
8185005: Improve performance of ThreadMXBean.getThreadInfo(long ids[], int maxDepth)
Reviewed-by: sspitsyn, dholmes, dcubed, rehn
1 parent c49aa4f commit 6ccf335

File tree

5 files changed

+334
-10
lines changed

5 files changed

+334
-10
lines changed

src/hotspot/share/runtime/mutexLocker.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Mutex* CodeHeapStateAnalytics_lock = NULL;
135135
Mutex* MetaspaceExpand_lock = NULL;
136136
Mutex* ClassLoaderDataGraph_lock = NULL;
137137
Monitor* ThreadsSMRDelete_lock = NULL;
138+
Mutex* ThreadIdTableCreate_lock = NULL;
138139
Mutex* SharedDecoder_lock = NULL;
139140
Mutex* DCmdFactory_lock = NULL;
140141
#if INCLUDE_NMT
@@ -317,6 +318,7 @@ void mutex_init() {
317318
def(CodeHeapStateAnalytics_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_never);
318319
def(NMethodSweeperStats_lock , PaddedMutex , special, true, Monitor::_safepoint_check_never);
319320
def(ThreadsSMRDelete_lock , PaddedMonitor, special, true, Monitor::_safepoint_check_never);
321+
def(ThreadIdTableCreate_lock , PaddedMutex , leaf, false, Monitor::_safepoint_check_always);
320322
def(SharedDecoder_lock , PaddedMutex , native, false, Monitor::_safepoint_check_never);
321323
def(DCmdFactory_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_never);
322324
#if INCLUDE_NMT

src/hotspot/share/runtime/mutexLocker.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ extern Monitor* Service_lock; // a lock used for service thre
115115
extern Monitor* PeriodicTask_lock; // protects the periodic task structure
116116
extern Monitor* RedefineClasses_lock; // locks classes from parallel redefinition
117117
extern Monitor* ThreadsSMRDelete_lock; // Used by ThreadsSMRSupport to take pressure off the Threads_lock
118+
extern Mutex* ThreadIdTableCreate_lock; // Used by ThreadIdTable to lazily create the thread id table
118119
extern Mutex* SharedDecoder_lock; // serializes access to the decoder during normal (not error reporting) use
119120
extern Mutex* DCmdFactory_lock; // serialize access to DCmdFactory information
120121
#if INCLUDE_NMT

src/hotspot/share/runtime/threadSMR.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
#include "logging/logStream.hpp"
2727
#include "memory/allocation.inline.hpp"
2828
#include "runtime/jniHandles.inline.hpp"
29+
#include "runtime/sharedRuntime.hpp"
2930
#include "runtime/thread.inline.hpp"
3031
#include "runtime/threadSMR.inline.hpp"
3132
#include "runtime/vmOperations.hpp"
33+
#include "services/threadIdTable.hpp"
3234
#include "services/threadService.hpp"
3335
#include "utilities/copy.hpp"
3436
#include "utilities/globalDefinitions.hpp"
@@ -129,7 +131,6 @@ uint ThreadsSMRSupport::_to_delete_list_cnt = 0;
129131
// Impl note: See _to_delete_list_cnt note.
130132
uint ThreadsSMRSupport::_to_delete_list_max = 0;
131133

132-
133134
// 'inline' functions first so the definitions are before first use:
134135

135136
inline void ThreadsSMRSupport::add_deleted_thread_times(uint add_value) {
@@ -608,16 +609,28 @@ int ThreadsList::find_index_of_JavaThread(JavaThread *target) {
608609
}
609610

610611
JavaThread* ThreadsList::find_JavaThread_from_java_tid(jlong java_tid) const {
611-
for (uint i = 0; i < length(); i++) {
612-
JavaThread* thread = thread_at(i);
613-
oop tobj = thread->threadObj();
614-
// Ignore the thread if it hasn't run yet, has exited
615-
// or is starting to exit.
616-
if (tobj != NULL && !thread->is_exiting() &&
617-
java_tid == java_lang_Thread::thread_id(tobj)) {
618-
// found a match
619-
return thread;
612+
ThreadIdTable::lazy_initialize(this);
613+
JavaThread* thread = ThreadIdTable::find_thread_by_tid(java_tid);
614+
if (thread == NULL) {
615+
// If the thread is not found in the table find it
616+
// with a linear search and add to the table.
617+
for (uint i = 0; i < length(); i++) {
618+
thread = thread_at(i);
619+
oop tobj = thread->threadObj();
620+
// Ignore the thread if it hasn't run yet, has exited
621+
// or is starting to exit.
622+
if (tobj != NULL && java_tid == java_lang_Thread::thread_id(tobj)) {
623+
MutexLocker ml(Threads_lock);
624+
// Must be inside the lock to ensure that we don't add a thread to the table
625+
// that has just passed the removal point in ThreadsSMRSupport::remove_thread()
626+
if (!thread->is_exiting()) {
627+
ThreadIdTable::add_thread(java_tid, thread);
628+
return thread;
629+
}
630+
}
620631
}
632+
} else if (!thread->is_exiting()) {
633+
return thread;
621634
}
622635
return NULL;
623636
}
@@ -742,6 +755,10 @@ void ThreadsSMRSupport::add_thread(JavaThread *thread){
742755

743756
ThreadsList *old_list = xchg_java_thread_list(new_list);
744757
free_list(old_list);
758+
if (ThreadIdTable::is_initialized()) {
759+
jlong tid = SharedRuntime::get_java_tid(thread);
760+
ThreadIdTable::add_thread(tid, thread);
761+
}
745762
}
746763

747764
// set_delete_notify() and clear_delete_notify() are called
@@ -909,6 +926,10 @@ void ThreadsSMRSupport::release_stable_list_wake_up(bool is_nested) {
909926
}
910927

911928
void ThreadsSMRSupport::remove_thread(JavaThread *thread) {
929+
if (ThreadIdTable::is_initialized()) {
930+
jlong tid = SharedRuntime::get_java_tid(thread);
931+
ThreadIdTable::remove_thread(tid);
932+
}
912933
ThreadsList *new_list = ThreadsList::remove_thread(ThreadsSMRSupport::get_java_thread_list(), thread);
913934
if (EnableThreadSMRStatistics) {
914935
ThreadsSMRSupport::inc_java_thread_list_alloc_cnt();
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
2+
/*
3+
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21+
* or visit www.oracle.com if you need additional information or have any
22+
* questions.
23+
*
24+
*/
25+
26+
#include "precompiled.hpp"
27+
#include "runtime/interfaceSupport.inline.hpp"
28+
#include "runtime/thread.hpp"
29+
#include "runtime/threadSMR.hpp"
30+
#include "runtime/timerTrace.hpp"
31+
#include "services/threadIdTable.hpp"
32+
#include "utilities/concurrentHashTable.inline.hpp"
33+
#include "utilities/concurrentHashTableTasks.inline.hpp"
34+
35+
36+
typedef ConcurrentHashTable<ThreadIdTableConfig, mtInternal> ThreadIdTableHash;
37+
38+
// 2^24 is max size
39+
static const size_t END_SIZE = 24;
40+
// Default initial size 256
41+
static const size_t DEFAULT_TABLE_SIZE_LOG = 8;
42+
// Prefer short chains of avg 2
43+
static const double PREF_AVG_LIST_LEN = 2.0;
44+
static ThreadIdTableHash* volatile _local_table = NULL;
45+
static volatile size_t _current_size = 0;
46+
static volatile size_t _items_count = 0;
47+
48+
volatile bool ThreadIdTable::_is_initialized = false;
49+
50+
class ThreadIdTableEntry : public CHeapObj<mtInternal> {
51+
private:
52+
jlong _tid;
53+
JavaThread* _java_thread;
54+
public:
55+
ThreadIdTableEntry(jlong tid, JavaThread* java_thread) :
56+
_tid(tid), _java_thread(java_thread) {}
57+
58+
jlong tid() const { return _tid; }
59+
JavaThread* thread() const { return _java_thread; }
60+
};
61+
62+
class ThreadIdTableConfig : public AllStatic {
63+
public:
64+
typedef ThreadIdTableEntry* Value;
65+
66+
static uintx get_hash(Value const& value, bool* is_dead) {
67+
jlong tid = value->tid();
68+
return primitive_hash(tid);
69+
}
70+
static void* allocate_node(size_t size, Value const& value) {
71+
ThreadIdTable::item_added();
72+
return AllocateHeap(size, mtInternal);
73+
}
74+
static void free_node(void* memory, Value const& value) {
75+
delete value;
76+
FreeHeap(memory);
77+
ThreadIdTable::item_removed();
78+
}
79+
};
80+
81+
static size_t ceil_log2(size_t val) {
82+
size_t ret;
83+
for (ret = 1; ((size_t)1 << ret) < val; ++ret);
84+
return ret;
85+
}
86+
87+
// Lazily creates the table and populates it with the given
88+
// thread list
89+
void ThreadIdTable::lazy_initialize(const ThreadsList *threads) {
90+
if (!_is_initialized) {
91+
{
92+
// There is no obvious benefits in allowing the thread table
93+
// to be concurently populated during the initalization.
94+
MutexLocker ml(ThreadIdTableCreate_lock);
95+
if (_is_initialized) {
96+
return;
97+
}
98+
create_table(threads->length());
99+
_is_initialized = true;
100+
}
101+
for (uint i = 0; i < threads->length(); i++) {
102+
JavaThread* thread = threads->thread_at(i);
103+
oop tobj = thread->threadObj();
104+
if (tobj != NULL) {
105+
jlong java_tid = java_lang_Thread::thread_id(tobj);
106+
MutexLocker ml(Threads_lock);
107+
if (!thread->is_exiting()) {
108+
// Must be inside the lock to ensure that we don't add a thread to the table
109+
// that has just passed the removal point in ThreadsSMRSupport::remove_thread()
110+
add_thread(java_tid, thread);
111+
}
112+
}
113+
}
114+
}
115+
}
116+
117+
void ThreadIdTable::create_table(size_t size) {
118+
assert(_local_table == NULL, "Thread table is already created");
119+
size_t size_log = ceil_log2(size);
120+
size_t start_size_log =
121+
size_log > DEFAULT_TABLE_SIZE_LOG ? size_log : DEFAULT_TABLE_SIZE_LOG;
122+
_current_size = (size_t)1 << start_size_log;
123+
_local_table = new ThreadIdTableHash(start_size_log, END_SIZE);
124+
}
125+
126+
void ThreadIdTable::item_added() {
127+
Atomic::inc(&_items_count);
128+
log_trace(thread, table) ("Thread entry added");
129+
}
130+
131+
void ThreadIdTable::item_removed() {
132+
Atomic::dec(&_items_count);
133+
log_trace(thread, table) ("Thread entry removed");
134+
}
135+
136+
double ThreadIdTable::get_load_factor() {
137+
return ((double)_items_count) / _current_size;
138+
}
139+
140+
size_t ThreadIdTable::table_size() {
141+
return (size_t)1 << _local_table->get_size_log2(Thread::current());
142+
}
143+
144+
void ThreadIdTable::grow(JavaThread* jt) {
145+
ThreadIdTableHash::GrowTask gt(_local_table);
146+
if (!gt.prepare(jt)) {
147+
return;
148+
}
149+
log_trace(thread, table)("Started to grow");
150+
TraceTime timer("Grow", TRACETIME_LOG(Debug, membername, table, perf));
151+
while (gt.do_task(jt)) {
152+
gt.pause(jt);
153+
{
154+
ThreadBlockInVM tbivm(jt);
155+
}
156+
gt.cont(jt);
157+
}
158+
gt.done(jt);
159+
_current_size = table_size();
160+
log_info(thread, table)("Grown to size:" SIZE_FORMAT, _current_size);
161+
}
162+
163+
class ThreadIdTableLookup : public StackObj {
164+
private:
165+
jlong _tid;
166+
uintx _hash;
167+
public:
168+
ThreadIdTableLookup(jlong tid)
169+
: _tid(tid), _hash(primitive_hash(tid)) {}
170+
uintx get_hash() const {
171+
return _hash;
172+
}
173+
bool equals(ThreadIdTableEntry** value, bool* is_dead) {
174+
bool equals = primitive_equals(_tid, (*value)->tid());
175+
if (!equals) {
176+
return false;
177+
}
178+
return true;
179+
}
180+
};
181+
182+
class ThreadGet : public StackObj {
183+
private:
184+
JavaThread* _return;
185+
public:
186+
ThreadGet(): _return(NULL) {}
187+
void operator()(ThreadIdTableEntry** val) {
188+
_return = (*val)->thread();
189+
}
190+
JavaThread* get_res_thread() {
191+
return _return;
192+
}
193+
};
194+
195+
void ThreadIdTable::grow_if_required() {
196+
assert(Thread::current()->is_Java_thread(),"Must be Java thread");
197+
assert(_is_initialized, "Thread table is not initialized");
198+
double load_factor = get_load_factor();
199+
log_debug(thread, table)("Concurrent work, load factor: %g", load_factor);
200+
if (load_factor > PREF_AVG_LIST_LEN && !_local_table->is_max_size_reached()) {
201+
grow(JavaThread::current());
202+
}
203+
}
204+
205+
JavaThread* ThreadIdTable::add_thread(jlong tid, JavaThread* java_thread) {
206+
assert(_is_initialized, "Thread table is not initialized");
207+
Thread* thread = Thread::current();
208+
ThreadIdTableLookup lookup(tid);
209+
ThreadGet tg;
210+
while (true) {
211+
if (_local_table->get(thread, lookup, tg)) {
212+
return tg.get_res_thread();
213+
}
214+
ThreadIdTableEntry* entry = new ThreadIdTableEntry(tid, java_thread);
215+
// The hash table takes ownership of the ThreadTableEntry,
216+
// even if it's not inserted.
217+
if (_local_table->insert(thread, lookup, entry)) {
218+
grow_if_required();
219+
return java_thread;
220+
}
221+
}
222+
}
223+
224+
JavaThread* ThreadIdTable::find_thread_by_tid(jlong tid) {
225+
assert(_is_initialized, "Thread table is not initialized");
226+
Thread* thread = Thread::current();
227+
ThreadIdTableLookup lookup(tid);
228+
ThreadGet tg;
229+
_local_table->get(thread, lookup, tg);
230+
return tg.get_res_thread();
231+
}
232+
233+
bool ThreadIdTable::remove_thread(jlong tid) {
234+
assert(_is_initialized, "Thread table is not initialized");
235+
Thread* thread = Thread::current();
236+
ThreadIdTableLookup lookup(tid);
237+
return _local_table->remove(thread, lookup);
238+
}

0 commit comments

Comments
 (0)