RFR: 8185005: Improve performance of ThreadMXBean.getThreadInfo(long ids[], int maxDepth)
Robbin Ehn
robbin.ehn at oracle.com
Wed Sep 25 06:45:37 UTC 2019
Hi Daniil,
Looks good, thanks!
/Robbin
On 9/25/19 12:45 AM, David Holmes wrote:
> Looks good to me.
>
> Thanks,
> David
>
> On 25/09/2019 2:36 am, Daniil Titov wrote:
>> Hi Daniel, David and Serguei,
>>
>> Please review a new version of the fix (webrev.08) that as Daniel suggested
>> renames
>> ThreadTable to ThreadIdTable (related classes and variables are renamed as
>> well) and
>> corrects formatting issues. There are no other changes in this webrev.08
>> comparing
>> to the previous version webrev.07.
>>
>> Testing: Mach5 tier1, tier2, tier3, tier4, and tier5 tests successfully passed.
>>
>> Webrev: https://cr.openjdk.java.net/~dtitov/8185005/webrev.08/
>> Bug: : https://bugs.openjdk.java.net/browse/JDK-8185005
>>
>> Thank you!
>>
>> Best regards,
>> Daniil
>>
>> On 9/20/19, 2:59 PM, "Daniel D. Daugherty" <daniel.daugherty at oracle.com> wrote:
>>
>> Daniil,
>> Thanks for sticking with this project through the many versions.
>> Sorry this review is late...
>> On 9/19/19 8:30 PM, Daniil Titov wrote:
>> > Hi David and Serguei,
>> >
>> > Please review new version of the fix that includes the changes Serguei
>> suggested:
>> > 1. If racing threads initialize the thread table only one of these
>> threads will populate the table with the threads from the thread list
>> > 2. The code that adds the thread to the tread table is put inside
>> Threads_lock to ensure that we cannot accidentally add the thread
>> > that has just passed the removal point in
>> ThreadsSMRSupport::remove_thread()
>> >
>> > The changes are in ThreadTable::lazy_initialize() method only.
>> >
>> > Testing: Mach5 tier1, tier2, tier3, tier4, and tier5 tests
>> successfully passed.
>> >
>> > Webrev: https://cr.openjdk.java.net/~dtitov/8185005/webrev.07/
>> src/hotspot/share/runtime/mutexLocker.hpp
>> No comments.
>> src/hotspot/share/runtime/mutexLocker.cpp
>> No comments.
>> src/hotspot/share/runtime/threadSMR.cpp
>> L623: MutexLocker ml(Threads_lock);
>> L626: if (!thread->is_exiting()) {
>> Re: discussion about is_exiting()
>> The header comment is pretty clear:
>> src/hotspot/share/runtime/thread.hpp:
>> // thread has called JavaThread::exit() or is terminated
>> bool is_exiting() const;
>> is_exiting() might become true right after you have called it,
>> but its purpose is to ask the question and not prevent the
>> condition from becoming true. As David said, you should consider
>> it an optimization. If you happen to see the condition is true,
>> then you know that the JavaThread isn't going to be around much
>> longer and should act accordingly.
>> The is_exiting() implementation is:
>> inline bool JavaThread::is_exiting() const {
>> // Use load-acquire so that setting of _terminated by
>> // JavaThread::exit() is seen more quickly.
>> TerminatedTypes l_terminated = (TerminatedTypes)
>> OrderAccess::load_acquire((volatile jint *) &_terminated);
>> return l_terminated == _thread_exiting ||
>> check_is_terminated(l_terminated);
>> }
>> and it depends on the JavaThread's _terminated field value.
>> // JavaThread termination support
>> enum TerminatedTypes {
>> _not_terminated = 0xDEAD - 2,
>> _thread_exiting, //
>> JavaThread::exit() has been called for this thread
>> _thread_terminated, // JavaThread
>> is removed from thread list
>> _vm_exited // JavaThread
>> is still executing native code, but VM is terminated
>> // only VM_Exit
>> can set _vm_exited
>> };
>> so the JavaThread's _terminated field can get set to
>> _thread_exiting independent of the Threads_lock, but
>> it can't get set to _thread_terminated without the
>> Threads_lock.
>> So by grabbing the Threads_lock on L623, you make sure
>> that ThreadTable::add_thread(java_tid, thread) does not
>> add a JavaThread that's not on the ThreadsList. It might
>> still become is_exiting() == true right after your
>> L626 if (!thread->is_exiting()) {
>> but it will still be on the main ThreadsList. And that
>> means that when the JavaThread is removed from the main
>> ThreadsList, you'll still call:
>> L931: ThreadTable::remove_thread(tid);
>> L624: // Must be inside the lock to ensure that we don't
>> add the thread to the table
>> typo: s/the thread/a thread/
>> L633: return thread;
>> nit - L633 - indented too far (should be 2 spaces)
>> src/hotspot/share/services/threadTable.hpp
>> L42: static void lazy_initialize(const ThreadsList *threads);
>> nit - put space between '*' the variable:
>> static void lazy_initialize(const ThreadsList* threads);
>> like you do in your other decls.
>> L45: // Lookup and inserts
>> Perhaps: // Lookup and list management
>> L60-61 - nit - please delete these blank lines.
>> src/hotspot/share/services/threadTable.cpp
>> L28: #include "runtime/timerTrace.hpp"
>> nit - This should be after threadSMR.hpp... (alpha sorted order)
>> L39: static const size_t DefaultThreadTableSizeLog = 8;
>> nit - your other 'static const' are not CamelCase. Why is this one?
>> L45: static ThreadTableHash* volatile _local_table = NULL;
>> L50: static volatile size_t _current_size = 0;
>> L51: static volatile size_t _items_count = 0;
>> nit - can you group the file statics together? (up with L41).
>> L60: _tid(tid),_java_thread(java_thread) {}
>> nit - space after ','
>> L62 jlong tid() const { return _tid;}
>> L63 JavaThread* thread() const {return _java_thread;}
>> nit - space before '}'
>> nit - space after '{' on L63.
>> L70: static uintx get_hash(Value const& value, bool* is_dead) {
>> Parameter 'is_dead' is not used.
>> L74: static void* allocate_node(size_t size, Value const& value) {
>> Parameter 'value' is not used.
>> L93: void ThreadTable::lazy_initialize(const ThreadsList *threads) {
>> Re: discussion about lazy_initialize() racing with
>> ThreadsList::find_JavaThread_from_java_tid()
>> There's a couple of aspects to these two pieces of code racing
>> with each other and racing with new thread creation. Racing with
>> new thread creation is the easy one:
>> If a new thread isn't added to the ThreadTable by
>> ThreadsSMRSupport::add_thread() calling
>> ThreadTable::add_thread(),
>> then the point in the future where someone calls
>> find_JavaThread_from_java_tid() will add it to the table due to
>> the linear search when ThreadTable::find_thread_by_tid()
>> returns NULL.
>> As for multi-threads calling
>> ThreadsList::find_JavaThread_from_java_tid()
>> at the same time which results in multi-threads in lazy_initialize()
>> at the same time...
>> - ThreadTable creation will be linear due to ThreadTableCreate_lock.
>> After _is_initialized is set to true, then no more callers to
>> lazy_initialize() will be in the "if (!_is_initialized)" block.
>> - Once the ThreadTable is created, then multi-threads can be
>> executing the for-loop to add their ThreadsList entries to
>> the ThreadTable. There will be a bit of Threads_lock contention
>> as each of the multi-threads tries to add their entries and
>> there will be some wasted work since the multi-threads will
>> likely have similar ThreadLists.
>> Of course, once _is_initialized is set to true, then any caller
>> to lazy_initialize() will return quickly and
>> ThreadsList::find_JavaThread_from_java_tid() will call
>> ThreadTable::find_thread_by_tid(). If the target java_tid isn't
>> found, then we do the linear search thing here and add the
>> the entry if we find a match in our current ThreadsList. Since
>> we're only adding the one here, we only contend for the Threads_lock
>> here if we find it.
>> If ThreadsList::find_JavaThread_from_java_tid() is called with a
>> target java_tid for a JavaThread that was created after the
>> ThreadsList object that the caller has in hand for the
>> find_JavaThread_from_java_tid() call, then, of course, that
>> target 'java_tid' won't be found because the JavaThread was
>> added the main ThreadsList _after_ the ThreadsList object was
>> created by the caller. Of course, you have to ask where the
>> target java_tid value came from since the JavaThread wasn't
>> around when the ThreadsList::find_JavaThread_from_java_tid()
>> call was made with that target java_tid value...
>> L99: // being concurently populated during the initalization.
>> Typos? Perhaps:
>> // to be concurrently populated during initialization.
>> But I think those two comment lines are more appropriate above
>> this line:
>> L96: MutexLocker ml(ThreadTableCreate_lock);
>> L112: // Must be inside the lock to ensure that we don't
>> add the thread to the table
>> typo: s/the thread/a thread/
>> L141: return ((double)_items_count)/_current_size;
>> nit - need spaces around '/'.
>> L177: bool equals(ThreadTableEntry **value, bool* is_dead) {
>> nit - put space between '**' the variable:
>> bool equals(ThreadTableEntry** value,
>> Parameter 'is_dead' is not used.
>> L214: while(true) {
>> nit - space before '('.
>> Short version: Thumbs up.
>> Longer version: I don't think I've spotted anything other than nits here.
>> Mostly I've just looked for multi-threaded races, proper usage of the
>> Thread-SMR stuff, and minimal impact in the case where the new
>> ThreadsTable is never needed.
>> Dan
>> P.S.
>> ThreadTable is a bit of misnomer. What you really have here is
>> a ThreadIdTable, but I'm really late to the code review flow
>> with that comment...
>> > Bug : https://bugs.openjdk.java.net/browse/JDK-8185005
>> >
>> > Thank you!
>> > --Daniil
>>
>>
More information about the serviceability-dev
mailing list