Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8185005: Improve performance of ThreadMXBean.getThreadInfo(long ids[], int maxDepth) #266

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -148,6 +148,7 @@ Monitor* CodeHeapStateAnalytics_lock = NULL;
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
@@ -349,6 +350,7 @@ void mutex_init() {
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, false, 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
@@ -127,6 +127,7 @@ extern Monitor* Service_lock; // a lock used for service thre
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
@@ -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 @@ uint ThreadsSMRSupport::_to_delete_list_cnt = 0;
// 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 @@ int ThreadsList::find_index_of_JavaThread(JavaThread *target) {
}

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 @@ void ThreadsSMRSupport::add_thread(JavaThread *thread){

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::release_stable_list_wake_up(bool is_nested) {
}

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();
@@ -0,0 +1,239 @@

/*
* 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"

class ThreadIdTableEntry;

typedef ConcurrentHashTable<ThreadIdTableEntry*, ThreadIdTableConfig, mtInternal> 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<mtInternal> {
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);
}