# HG changeset patch # User dtitov # Date 1569435005 25200 # Node ID f4abe950c3b0f8d4484083e67711015326cca341 # Parent c7d9df2e470c43d4d3db9bb7a00512bc9d94c7f7 8185005: Improve performance of ThreadMXBean.getThreadInfo(long ids[], int maxDepth) Reviewed-by: sspitsyn, dholmes, dcubed, rehn diff -r c7d9df2e470c -r f4abe950c3b0 src/hotspot/share/runtime/mutexLocker.cpp --- a/src/hotspot/share/runtime/mutexLocker.cpp Wed Sep 25 12:21:10 2019 +0200 +++ b/src/hotspot/share/runtime/mutexLocker.cpp Wed Sep 25 11:10:05 2019 -0700 @@ -135,6 +135,7 @@ Mutex* MetaspaceExpand_lock = NULL; Mutex* ClassLoaderDataGraph_lock = NULL; Monitor* ThreadsSMRDelete_lock = NULL; +Mutex* ThreadIdTableCreate_lock = NULL; Mutex* SharedDecoder_lock = NULL; Mutex* DCmdFactory_lock = NULL; #if INCLUDE_NMT @@ -317,6 +318,7 @@ def(CodeHeapStateAnalytics_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_never); def(NMethodSweeperStats_lock , PaddedMutex , special, true, Monitor::_safepoint_check_never); def(ThreadsSMRDelete_lock , PaddedMonitor, special, true, Monitor::_safepoint_check_never); + def(ThreadIdTableCreate_lock , PaddedMutex , leaf, false, Monitor::_safepoint_check_always); def(SharedDecoder_lock , PaddedMutex , native, false, Monitor::_safepoint_check_never); def(DCmdFactory_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_never); #if INCLUDE_NMT diff -r c7d9df2e470c -r f4abe950c3b0 src/hotspot/share/runtime/mutexLocker.hpp --- a/src/hotspot/share/runtime/mutexLocker.hpp Wed Sep 25 12:21:10 2019 +0200 +++ b/src/hotspot/share/runtime/mutexLocker.hpp Wed Sep 25 11:10:05 2019 -0700 @@ -115,6 +115,7 @@ extern Monitor* PeriodicTask_lock; // protects the periodic task structure extern Monitor* RedefineClasses_lock; // locks classes from parallel redefinition extern Monitor* ThreadsSMRDelete_lock; // Used by ThreadsSMRSupport to take pressure off the Threads_lock +extern Mutex* ThreadIdTableCreate_lock; // Used by ThreadIdTable to lazily create the thread id table extern Mutex* SharedDecoder_lock; // serializes access to the decoder during normal (not error reporting) use extern Mutex* DCmdFactory_lock; // serialize access to DCmdFactory information #if INCLUDE_NMT diff -r c7d9df2e470c -r f4abe950c3b0 src/hotspot/share/runtime/threadSMR.cpp --- a/src/hotspot/share/runtime/threadSMR.cpp Wed Sep 25 12:21:10 2019 +0200 +++ b/src/hotspot/share/runtime/threadSMR.cpp Wed Sep 25 11:10:05 2019 -0700 @@ -26,9 +26,11 @@ #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/sharedRuntime.hpp" #include "runtime/thread.inline.hpp" #include "runtime/threadSMR.inline.hpp" #include "runtime/vmOperations.hpp" +#include "services/threadIdTable.hpp" #include "services/threadService.hpp" #include "utilities/copy.hpp" #include "utilities/globalDefinitions.hpp" @@ -129,7 +131,6 @@ // Impl note: See _to_delete_list_cnt note. uint ThreadsSMRSupport::_to_delete_list_max = 0; - // 'inline' functions first so the definitions are before first use: inline void ThreadsSMRSupport::add_deleted_thread_times(uint add_value) { @@ -608,16 +609,28 @@ } JavaThread* ThreadsList::find_JavaThread_from_java_tid(jlong java_tid) const { - for (uint i = 0; i < length(); i++) { - JavaThread* thread = thread_at(i); - oop tobj = thread->threadObj(); - // Ignore the thread if it hasn't run yet, has exited - // or is starting to exit. - if (tobj != NULL && !thread->is_exiting() && - java_tid == java_lang_Thread::thread_id(tobj)) { - // found a match - return thread; + ThreadIdTable::lazy_initialize(this); + JavaThread* thread = ThreadIdTable::find_thread_by_tid(java_tid); + if (thread == NULL) { + // If the thread is not found in the table find it + // with a linear search and add to the table. + for (uint i = 0; i < length(); i++) { + thread = thread_at(i); + oop tobj = thread->threadObj(); + // Ignore the thread if it hasn't run yet, has exited + // or is starting to exit. + if (tobj != NULL && java_tid == java_lang_Thread::thread_id(tobj)) { + MutexLocker ml(Threads_lock); + // Must be inside the lock to ensure that we don't add a thread to the table + // that has just passed the removal point in ThreadsSMRSupport::remove_thread() + if (!thread->is_exiting()) { + ThreadIdTable::add_thread(java_tid, thread); + return thread; + } + } } + } else if (!thread->is_exiting()) { + return thread; } return NULL; } @@ -742,6 +755,10 @@ ThreadsList *old_list = xchg_java_thread_list(new_list); free_list(old_list); + if (ThreadIdTable::is_initialized()) { + jlong tid = SharedRuntime::get_java_tid(thread); + ThreadIdTable::add_thread(tid, thread); + } } // set_delete_notify() and clear_delete_notify() are called @@ -909,6 +926,10 @@ } void ThreadsSMRSupport::remove_thread(JavaThread *thread) { + if (ThreadIdTable::is_initialized()) { + jlong tid = SharedRuntime::get_java_tid(thread); + ThreadIdTable::remove_thread(tid); + } ThreadsList *new_list = ThreadsList::remove_thread(ThreadsSMRSupport::get_java_thread_list(), thread); if (EnableThreadSMRStatistics) { ThreadsSMRSupport::inc_java_thread_list_alloc_cnt(); diff -r c7d9df2e470c -r f4abe950c3b0 src/hotspot/share/services/threadIdTable.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/services/threadIdTable.cpp Wed Sep 25 11:10:05 2019 -0700 @@ -0,0 +1,238 @@ + +/* +* Copyright (c) 2019, 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 "precompiled.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/thread.hpp" +#include "runtime/threadSMR.hpp" +#include "runtime/timerTrace.hpp" +#include "services/threadIdTable.hpp" +#include "utilities/concurrentHashTable.inline.hpp" +#include "utilities/concurrentHashTableTasks.inline.hpp" + + +typedef ConcurrentHashTable ThreadIdTableHash; + +// 2^24 is max size +static const size_t END_SIZE = 24; +// Default initial size 256 +static const size_t DEFAULT_TABLE_SIZE_LOG = 8; +// Prefer short chains of avg 2 +static const double PREF_AVG_LIST_LEN = 2.0; +static ThreadIdTableHash* volatile _local_table = NULL; +static volatile size_t _current_size = 0; +static volatile size_t _items_count = 0; + +volatile bool ThreadIdTable::_is_initialized = false; + +class ThreadIdTableEntry : public CHeapObj { +private: + jlong _tid; + JavaThread* _java_thread; +public: + ThreadIdTableEntry(jlong tid, JavaThread* java_thread) : + _tid(tid), _java_thread(java_thread) {} + + jlong tid() const { return _tid; } + JavaThread* thread() const { return _java_thread; } +}; + +class ThreadIdTableConfig : public AllStatic { + public: + typedef ThreadIdTableEntry* Value; + + static uintx get_hash(Value const& value, bool* is_dead) { + jlong tid = value->tid(); + return primitive_hash(tid); + } + static void* allocate_node(size_t size, Value const& value) { + ThreadIdTable::item_added(); + return AllocateHeap(size, mtInternal); + } + static void free_node(void* memory, Value const& value) { + delete value; + FreeHeap(memory); + ThreadIdTable::item_removed(); + } +}; + +static size_t ceil_log2(size_t val) { + size_t ret; + for (ret = 1; ((size_t)1 << ret) < val; ++ret); + return ret; +} + +// Lazily creates the table and populates it with the given +// thread list +void ThreadIdTable::lazy_initialize(const ThreadsList *threads) { + if (!_is_initialized) { + { + // There is no obvious benefits in allowing the thread table + // to be concurently populated during the initalization. + MutexLocker ml(ThreadIdTableCreate_lock); + if (_is_initialized) { + return; + } + create_table(threads->length()); + _is_initialized = true; + } + for (uint i = 0; i < threads->length(); i++) { + JavaThread* thread = threads->thread_at(i); + oop tobj = thread->threadObj(); + if (tobj != NULL) { + jlong java_tid = java_lang_Thread::thread_id(tobj); + MutexLocker ml(Threads_lock); + if (!thread->is_exiting()) { + // Must be inside the lock to ensure that we don't add a thread to the table + // that has just passed the removal point in ThreadsSMRSupport::remove_thread() + add_thread(java_tid, thread); + } + } + } + } +} + +void ThreadIdTable::create_table(size_t size) { + assert(_local_table == NULL, "Thread table is already created"); + size_t size_log = ceil_log2(size); + size_t start_size_log = + size_log > DEFAULT_TABLE_SIZE_LOG ? size_log : DEFAULT_TABLE_SIZE_LOG; + _current_size = (size_t)1 << start_size_log; + _local_table = new ThreadIdTableHash(start_size_log, END_SIZE); +} + +void ThreadIdTable::item_added() { + Atomic::inc(&_items_count); + log_trace(thread, table) ("Thread entry added"); +} + +void ThreadIdTable::item_removed() { + Atomic::dec(&_items_count); + log_trace(thread, table) ("Thread entry removed"); +} + +double ThreadIdTable::get_load_factor() { + return ((double)_items_count) / _current_size; +} + +size_t ThreadIdTable::table_size() { + return (size_t)1 << _local_table->get_size_log2(Thread::current()); +} + +void ThreadIdTable::grow(JavaThread* jt) { + ThreadIdTableHash::GrowTask gt(_local_table); + if (!gt.prepare(jt)) { + return; + } + log_trace(thread, table)("Started to grow"); + TraceTime timer("Grow", TRACETIME_LOG(Debug, membername, table, perf)); + while (gt.do_task(jt)) { + gt.pause(jt); + { + ThreadBlockInVM tbivm(jt); + } + gt.cont(jt); + } + gt.done(jt); + _current_size = table_size(); + log_info(thread, table)("Grown to size:" SIZE_FORMAT, _current_size); +} + +class ThreadIdTableLookup : public StackObj { +private: + jlong _tid; + uintx _hash; +public: + ThreadIdTableLookup(jlong tid) + : _tid(tid), _hash(primitive_hash(tid)) {} + uintx get_hash() const { + return _hash; + } + bool equals(ThreadIdTableEntry** value, bool* is_dead) { + bool equals = primitive_equals(_tid, (*value)->tid()); + if (!equals) { + return false; + } + return true; + } +}; + +class ThreadGet : public StackObj { +private: + JavaThread* _return; +public: + ThreadGet(): _return(NULL) {} + void operator()(ThreadIdTableEntry** val) { + _return = (*val)->thread(); + } + JavaThread* get_res_thread() { + return _return; + } +}; + +void ThreadIdTable::grow_if_required() { + assert(Thread::current()->is_Java_thread(),"Must be Java thread"); + assert(_is_initialized, "Thread table is not initialized"); + double load_factor = get_load_factor(); + log_debug(thread, table)("Concurrent work, load factor: %g", load_factor); + if (load_factor > PREF_AVG_LIST_LEN && !_local_table->is_max_size_reached()) { + grow(JavaThread::current()); + } +} + +JavaThread* ThreadIdTable::add_thread(jlong tid, JavaThread* java_thread) { + assert(_is_initialized, "Thread table is not initialized"); + Thread* thread = Thread::current(); + ThreadIdTableLookup lookup(tid); + ThreadGet tg; + while (true) { + if (_local_table->get(thread, lookup, tg)) { + return tg.get_res_thread(); + } + ThreadIdTableEntry* entry = new ThreadIdTableEntry(tid, java_thread); + // The hash table takes ownership of the ThreadTableEntry, + // even if it's not inserted. + if (_local_table->insert(thread, lookup, entry)) { + grow_if_required(); + return java_thread; + } + } +} + +JavaThread* ThreadIdTable::find_thread_by_tid(jlong tid) { + assert(_is_initialized, "Thread table is not initialized"); + Thread* thread = Thread::current(); + ThreadIdTableLookup lookup(tid); + ThreadGet tg; + _local_table->get(thread, lookup, tg); + return tg.get_res_thread(); +} + +bool ThreadIdTable::remove_thread(jlong tid) { + assert(_is_initialized, "Thread table is not initialized"); + Thread* thread = Thread::current(); + ThreadIdTableLookup lookup(tid); + return _local_table->remove(thread, lookup); +} diff -r c7d9df2e470c -r f4abe950c3b0 src/hotspot/share/services/threadIdTable.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/services/threadIdTable.hpp Wed Sep 25 11:10:05 2019 -0700 @@ -0,0 +1,62 @@ + +/* +* Copyright (c) 2019, 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. +* +*/ + +#ifndef SHARE_SERVICES_THREADIDTABLE_HPP +#define SHARE_SERVICES_THREADIDTABLE_HPP + +#include "memory/allocation.hpp" + +class JavaThread; +class ThreadsList; +class ThreadIdTableConfig; + +class ThreadIdTable : public AllStatic { + friend class ThreadIdTableConfig; + + static volatile bool _is_initialized; + +public: + // Initialization + static void lazy_initialize(const ThreadsList* threads); + static bool is_initialized() { return _is_initialized; } + + // Lookup and list management + static JavaThread* find_thread_by_tid(jlong tid); + static JavaThread* add_thread(jlong tid, JavaThread* thread); + static bool remove_thread(jlong tid); + +private: + static void create_table(size_t size); + + static size_t table_size(); + static double get_load_factor(); + static void grow_if_required(); + static void grow(JavaThread* jt); + + static void item_added(); + static void item_removed(); +}; + +#endif // SHARE_SERVICES_THREADIDTABLE_HPP