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