RFR: 8185005: Improve performance of ThreadMXBean.getThreadInfo(long ids[], int maxDepth)
David Holmes
david.holmes at oracle.com
Sat Sep 21 02:20:18 UTC 2019
Hi Serguei,
On 21/09/2019 9:50 am, serguei.spitsyn at oracle.com wrote:
> Hi Daniil,
>
> Yes, the Threads_lock is still needed around thread->is_exiting() check
> and add_thread().
>
>> And if we try to use 2 locks, ThreadTableCreate_lock as in your snippet
>> and then the nested Threads_lock around thread->is_exiting() and
>> add_thread(java_tid, thread) lines then it will not work since the rank
>> of Threads_lock is higher than the rank of ThreadTableCreate_lock.
>
> The ThreadTableCreate_lock is only used in the lazy_initialize() function.
> It looks safe to adjust the ThreadTableCreate_lock to fix the above
> problem.
> Then the approach below will work as needed.
>
> void ThreadTable::lazy_initialize(const ThreadsList *threads) {
> if (_is_initialized) {
> return;
> }
> MutexLocker ml(ThreadTableCreate_lock);
> if (_is_initialized) {
> // There is no obvious benefits in allowing the thread table
> // being concurrently populated during the initalization.
> 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 the
> thread to the table
> // that has just passed the removal point in
> ThreadsSMRSupport::remove_thread()
> add_thread(java_tid, thread);
> }
> }
> }
> }
I don't like holding a completely unrelated lock, whilst holding the
Threads_lock, particularly as it seems unnecessary for correctness. We
have to be certain that no M&M operation that holds the Threads_lock can
try to initialize the thread table. Testing can only prove the presence
of that problem, not the absence.
I think Daniil's webrev.07 version is correct and less risky than what
you propose. Sorry.
David
-----
>
> If you rename ThreadTable to ThreadIdTable then the ThreadTableCreate_lock
> has to be renamed to ThreadIdTableCreate_lock.
>
> Thanks,
> Serguei
>
> On 9/20/19 8:42 AM, Daniil Titov wrote:
>> Hi Serguei,
>>
>>> void ThreadTable::lazy_initialize(const ThreadsList *threads) {
>>> if (_is_initialized) {
>>> return;
>> > }
>>> MutexLocker ml(ThreadTableCreate_lock);
>>
>> If I understood you correctly in the code snippet you sent you meant
>> to use
>> Threads_lock, not ThreadTableCreate_lock, right?
>>
>> The original idea was to do a minimal amount of work while holding the
>> lock and
>> hold the lock for as short period of time as possible to not block
>> other threads when it is not necessary.
>>
>> With the suggested approach no new threads could be started until the
>> thread table is created and
>> populated with all threads running inside a Java application and in
>> case of large app there could be
>> thousands of them.
>>
>> And if we try to use 2 locks, ThreadTableCreate_lock as in your
>> snippet and then the nested Threads_lock around
>> thread->is_exiting() and add_thread(java_tid, thread) lines then it
>> will not work since the rank of Threads_lock
>> is higher than the rank of ThreadTableCreate_lock.
>>
>> So choosing between blocking new threads from starting and potentially
>> allowing
>> some other monitoring thread to do a one-time linear scan I think it
>> makes sense to choose the latter.
>>
>> Thanks!
>>
>> Best regards,
>> Daniil
>>
>>
>>
>> From: "serguei.spitsyn at oracle.com" <serguei.spitsyn at oracle.com>
>> Date: Thursday, September 19, 2019 at 10:30 PM
>> To: Daniil Titov <daniil.x.titov at oracle.com>, Robbin Ehn
>> <robbin.ehn at oracle.com>, David Holmes <david.holmes at oracle.com>,
>> <daniel.daugherty at oracle.com>, OpenJDK Serviceability
>> <serviceability-dev at openjdk.java.net>,
>> "hotspot-runtime-dev at openjdk.java.net"
>> <hotspot-runtime-dev at openjdk.java.net>, "jmx-dev at openjdk.java.net"
>> <jmx-dev at openjdk.java.net>, Claes Redestad <claes.redestad at oracle.com>
>> Subject: Re: RFR: 8185005: Improve performance of
>> ThreadMXBean.getThreadInfo(long ids[], int maxDepth)
>>
>> Hi Daniil,
>>
>> I think, it is better to grab the thread_lock just once at lazy
>> initialization.
>> It would look simpler, something, like this would work:
>> void ThreadTable::lazy_initialize(const ThreadsList *threads) {
>> if (_is_initialized) {
>> return;
>> }
>> MutexLocker ml(ThreadTableCreate_lock);
>> if (_is_initialized) {
>> // There is no obvious benefits in allowing the thread table
>> // being concurrently populated during the initalization.
>> 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);
>> if (!thread->is_exiting()) {
>> // Must be inside the lock to ensure that we don't add the
>> thread to the table
>> // that has just passed the removal point in
>> ThreadsSMRSupport::remove_thread()
>> add_thread(java_tid, thread);
>> }
>> }
>> }
>> }
>>
>> Otherwise, concurrent executions of the find_JavaThread_from_java_tid()
>> will sometimes do a linear search of threads that are not included yet to
>> the ThreadTable from the ThreadsList (which is used for lazy
>> initialization).
>> Instead, it is better to wait for the lazy_initialization() to complete.
>> Thanks,
>> Serguei
>>
>>
>> On 9/19/19 17:30, 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/
>> Bug : https://bugs.openjdk.java.net/browse/JDK-8185005
>>
>> Thank you!
>> --Daniil
>>
>> On 9/18/19, 1:01 AM, mailto:serguei.spitsyn at oracle.com
>> mailto:serguei.spitsyn at oracle.com wrote:
>>
>> Hi Daniil,
>> On 9/17/19 17:13, Daniil Titov wrote:
>> > Hi Serguei,
>> >
>> > Please find below my answers to the concerns you mentioned in
>> the previous email.
>> >
>> > 1.
>> > > I have a concern about the checks for thread->is_exiting().
>> > > - the lines 632-633 are useless as they do not really
>> protect from returning an exiting thread
>> >> It is interesting what might happen if an exiting thread is
>> returned by the
>> >> ThreadsList::find_JavaThread_from_java_tid ().
>> >> Does it make sense to develop a test that would cover these
>> cases?
>> > I agree, it doesn't really provide any protection so it makes
>> sense just remove it.
>> Now, I'm not that confident about it. :)
>> > The current implementation
>> > find_JavaThread_from_java_tid() doesn't provide such
>> protection as well, since the thread could start exiting
>> > immediately after method find_JavaThread_from_java_tid()
>> returns, so the assumption is that the callers of
>> > find_JavaThread_from_java_tid() are expecting to deal with
>> such threads and looking on some of them shows that
>> > they usually try to retrieve threadObj or a thread statistic
>> object and if it is NULL that just do nothing.
>> If I understand it correctly, the jt->threadObj() can remain
>> non-NULL
>> for some time while jt->is_exiting() == true.
>> It is not clear how reliable is to use it.
>> But this is a pre-existing issue. It is not you who introduced
>> it. :)
>> So, we can skip it for now.
>> But for the record, we may have a source of intermittent issues.
>> > I'm not sure we could cover this specific case with the test.
>> The window between find_JavaThread_from_java_tid() returns and the caller
>> > continues the execution is too small. The window between the
>> thread started exiting and removed itself from the thread table is
>> very small as well.
>> Understand.
>> > 2.
>> >> - the lines 105-108 can result in adding exiting threads
>> into the ThreadTable
>> > I agree, it was missed, we need to wrap this code inside
>> Thread_lock in the similar way as it is done
>> find_JavaThread_from_java_tid()
>> Okay, thanks!
>> > 3.
>> >> I would suggest to rewrite this fragment in a safe way:
>> >> 95 {
>> >> 96 MutexLocker ml(ThreadTableCreate_lock);
>> >> 97 if (!_is_initialized) {
>> >> 98 create_table(threads->length());
>> >> 99 _is_initialized = true;
>> >> 100 }
>> >> 101 }
>> >> as:
>> >> {
>> >> MutexLocker ml(ThreadTableCreate_lock);
>> >> if (_is_initialized) {
>> >> return;
>> > > }
>> > > create_table(threads->length());
>> > > _is_initialized = true;
>> > > }
>> >
>> > It was an intension to not block while populating the table
>> with the threads from the current thread list.
>> > There is no needs to have other threads that call
>> find_JavaThread_from_java_tid() be blocked and waiting for
>> > it to complete since the requested thread could be not
>> present in the thread list that triggers the thread table
>> > initialization. Plus in case of racing initialization it
>> allows threads from not original thread lists be added to the table
>> > and thus avoid the linear scan when these thread are looked up
>> for the first time.
>> I've replied to David in another email.
>> Let's talk once more about it tomorrow.
>> > 4.
>> >>> The case you have described is exact the reason why we still
>> have a code inside
>> >>> ThreadsList::find_JavaThread_from_java_tid() method that does
>> a linear scan and adds
>> >>> the requested thread to the thread table if it is not
>> there ( lines 614-613 below).
>> >> I disagree because it is easy to avoid concurrent ThreadTable
>> >> initialization (please, see my separate email).
>> >> The reason for this code is to cover a case of late/lazy
>> ThreadTable
>> >> initialization.
>> > David Holmes replied to this in a separate email providing a
>> very detailed
>> > explanation of the possible cases and how the proposed
>> implementation satisfies them.
>> Yes. Please, see above.
>> Thanks,
>> Serguei
>> > Best regards,
>> > Daniil
>> >
>> > From: mailto:serguei.spitsyn at oracle.com
>> mailto:serguei.spitsyn at oracle.com
>> > Date: Tuesday, September 17, 2019 at 1:53 AM
>> > To: Daniil Titov mailto:daniil.x.titov at oracle.com, Robbin Ehn
>> mailto:robbin.ehn at oracle.com, David Holmes
>> mailto:david.holmes at oracle.com, mailto:daniel.daugherty at oracle.com,
>> OpenJDK Serviceability mailto:serviceability-dev at openjdk.java.net,
>> mailto:hotspot-runtime-dev at openjdk.java.net
>> mailto:hotspot-runtime-dev at openjdk.java.net,
>> mailto:jmx-dev at openjdk.java.net mailto:jmx-dev at openjdk.java.net, Claes
>> Redestad mailto:claes.redestad at oracle.com
>> > Subject: Re: RFR: 8185005: Improve performance of
>> ThreadMXBean.getThreadInfo(long ids[], int maxDepth)
>> >
>> > Hi Daniil,
>> >
>> > Thank you for you patience in working on this issue!
>> > Also, I like that the current thread related optimizations in
>> management.cpp were factored out.
>> > It was a good idea to separate them.
>> >
>> > I have a concern about the checks for thread->is_exiting().
>> > The threads are added to and removed from the ThreadTable under
>> protection of Threads_lock.
>> > However, the thread->is_exiting() checks are not protected, and
>> so, they are racy.
>> >
>> > There is a couple of such checks to mention:
>> > 611 JavaThread*
>> ThreadsList::find_JavaThread_from_java_tid(jlong java_tid) const {
>> > 612 ThreadTable::lazy_initialize(this);
>> > 613 JavaThread* thread =
>> ThreadTable::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 the thread to the table
>> > 625 // that has just passed the removal point in
>> ThreadsSMRSupport::remove_thread()
>> > 626 if (!thread->is_exiting()) {
>> > 627 ThreadTable::add_thread(java_tid, thread);
>> > 628 return thread;
>> > 629 }
>> > 630 }
>> > 631 }
>> > 632 } else if (!thread->is_exiting()) {
>> > 633 return thread;
>> > 634 }
>> > 635 return NULL;
>> > 636 }
>> > ...
>> > 93 void ThreadTable::lazy_initialize(const ThreadsList
>> *threads) {
>> > 94 if (!_is_initialized) {
>> > 95 {
>> > 96 MutexLocker ml(ThreadTableCreate_lock);
>> > 97 if (!_is_initialized) {
>> > 98 create_table(threads->length());
>> > 99 _is_initialized = true;
>> > 100 }
>> > 101 }
>> > 102 for (uint i = 0; i < threads->length(); i++) {
>> > 103 JavaThread* thread = threads->thread_at(i);
>> > 104 oop tobj = thread->threadObj();
>> > 105 if (tobj != NULL && !thread->is_exiting()) {
>> > 106 jlong java_tid = java_lang_Thread::thread_id(tobj);
>> > 107 add_thread(java_tid, thread);
>> > 108 }
>> > 109 }
>> > 110 }
>> > 111 }
>> >
>> > A thread may start exiting right after the checks at the lines
>> 626 and 105.
>> > So that:
>> > - the lines 632-633 are useless as they do not really protect
>> from returning an exiting thread
>> > - the lines 105-108 can result in adding exiting threads into
>> the ThreadTable
>> >
>> > Please, note, the lines 626-629 are safe in terms of addition
>> to the ThreadTable as they
>> > are protected with the Threads_lock. But the returned thread
>> still can exit after that.
>> > It is interesting what might happen if an exiting thread is
>> returned by the
>> > ThreadsList::find_JavaThread_from_java_tid ().
>> >
>> > Does it make sense to develop a test that would cover these cases?
>> >
>> > Thanks,
>> > Serguei
>> >
>> >
>> > On 9/16/19 11:18, Daniil Titov wrote:
>> > Hello,
>> >
>> > After investigating with Claes the impact of this change on the
>> performance (thanks a lot Claes for helping with it!) the conclusion
>> was that the impact on the thread startup time is not a blocker for
>> this change.
>> >
>> > I also measured the memory footprint using Native Memory
>> Tracking and results showed around 40 bytes per live thread.
>> >
>> > Please review a new version of the fix, webrev.06 [1]. Just to
>> remind, webrev.05 was abandoned and webrev.06 [1] is webrev.04 [3]
>> minus changes in src/hotspot/share/services/management.cpp (that were
>> factored out to a separate issue [4]) and plus a change in
>> ThreadsList::find_JavaThread_from_java_tid() method (please, see
>> below) that addresses the problem Robbin found and puts the code that
>> adds a new thread to the thread table inside Threads_lock.
>> >
>> > src/hotspot/share/runtime/threadSMR.cpp
>> >
>> > 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 the thread to the table
>> > 625 // that has just passed the removal point in
>> ThreadsSMRSupport::remove_thread()
>> > 626 if (!thread->is_exiting()) {
>> > 627 ThreadTable::add_thread(java_tid, thread);
>> > 628 return thread;
>> > 629 }
>> > 630 }
>> >
>> > [1] Webrev: https://cr.openjdk.java.net/~dtitov/8185005/webrev.06
>> > [2] Bug: https://bugs.openjdk.java.net/browse/JDK-8185005
>> > [3] https://cr.openjdk.java.net/~dtitov/8185005/webrev.04
>> > [4] https://bugs.openjdk.java.net/browse/JDK-8229391
>> >
>> > Thank you,
>> > Daniil
>> >
>> >
>> >
>> > >
>> > > On 8/4/19, 7:54 PM, "David Holmes"
>> mailto:david.holmes at oracle.com wrote:
>> > >
>> > > Hi Daniil,
>> > >
>> > > On 3/08/2019 8:16 am, Daniil Titov wrote:
>> > > > Hi David,
>> > > >
>> > > > Thank you for your detailed review. Please
>> review a new version of the fix that includes
>> > > > the changes you suggested:
>> > > > - ThreadTableCreate_lock scope is reduced to
>> cover the creation of the table only;
>> > > > - ThreadTableCreate_lock is made
>> _safepoint_check_always;
>> > >
>> > > Okay.
>> > >
>> > > > - ServiceThread is no longer responsible for
>> the resizing of the thread table, instead,
>> > > > the thread table is changed to grow on
>> demand by the thread that is doing the addition;
>> > >
>> > > Okay - I'm happy to get the serviceThread out
>> of the picture here.
>> > >
>> > > > - fixed nits and formatting issues.
>> > >
>> > > Okay.
>> > >
>> > > >>> The change also includes additional
>> optimization for some callers of find_JavaThread_from_java_tid()
>> > > >>> as Daniel suggested.
>> > > >> Not sure it's best to combine these, but if
>> they are limited to the
>> > > >> changes in management.cpp only then that may
>> be okay.
>> > > >
>> > > > The additional optimization for some callers
>> of find_JavaThread_from_java_tid() is
>> > > > limited to management.cpp (plus a new test)
>> so I left them in the webrev but
>> > > > I also could move it in the separate issue if
>> required.
>> > >
>> > > I'd prefer this part of be separated out, but
>> won't insist. Let's see if
>> > > Dan or Serguei have a strong opinion.
>> > >
>> > > > > src/hotspot/share/runtime/threadSMR.cpp
>> > > > >755 jlong tid =
>> SharedRuntime::get_java_tid(thread);
>> > > > > 926 jlong tid =
>> SharedRuntime::get_java_tid(thread);
>> > > > > I think it cleaner/better to just use
>> > > > > jlong tid =
>> java_lang_Thread::thread_id(thread->threadObj());
>> > > > > as we know thread is not NULL, it is a
>> JavaThread and it has to have a
>> > > > > non-null threadObj.
>> > > >
>> > > > I had to leave this code unchanged since it
>> turned out the threadObj is null
>> > > > when VM is destroyed:
>> > > >
>> > > > V [libjvm.so+0xe165d7]
>> oopDesc::long_field(int) const+0x67
>> > > > V [libjvm.so+0x16e06c6]
>> ThreadsSMRSupport::add_thread(JavaThread*)+0x116
>> > > > V [libjvm.so+0x16d1302]
>> Threads::add(JavaThread*, bool)+0x82
>> > > > V [libjvm.so+0xef8369]
>> attach_current_thread.part.197+0xc9
>> > > > V [libjvm.so+0xec136c] jni_DestroyJavaVM+0x6c
>> > > > C [libjli.so+0x4333] JavaMain+0x2c3
>> > > > C [libjli.so+0x8159] ThreadJavaMain+0x9
>> > >
>> > > This is actually nothing to do with the VM
>> being destroyed, but is an
>> > > issue with JNI_AttachCurrentThread and its
>> interaction with the
>> > > ThreadSMR iterators. The attach process is:
>> > > - create JavaThread
>> > > - mark as "is attaching via jni"
>> > > - add to ThreadsList
>> > > - create java.lang.Thread object (you can only
>> execute Java code after
>> > > you are attached)
>> > > - mark as "attach completed"
>> > >
>> > > So while a thread "is attaching" it will be
>> seen by the ThreadSMR thread
>> > > iterator but will have a NULL java.lang.Thread
>> object.
>> > >
>> > > We special-case attaching threads in a number
>> of places in the VM and I
>> > > think we should be explicitly doing something
>> here to filter out
>> > > attaching threads, rather than just being
>> tolerant of a NULL j.l.Thread
>> > > object. Specifically in
>> ThreadsSMRSupport::add_thread:
>> > >
>> > > if (ThreadTable::is_initialized() &&
>> !thread->is_attaching_via_jni()) {
>> > > jlong tid =
>> java_lang_Thread::thread_id(thread->threadObj());
>> > > ThreadTable::add_thread(tid, thread);
>> > > }
>> > >
>> > > Note that in ThreadsSMRSupport::remove_thread
>> we can use the same guard,
>> > > which covers the case the JNI attach
>> encountered an error trying to
>> > > create the j.l.Thread object.
>> > >
>> > > >> src/hotspot/share/services/threadTable.cpp
>> > > >> 71 static uintx get_hash(Value const&
>> value, bool* is_dead) {
>> > > >
>> > > >> The is_dead parameter still bothers me here.
>> I can't make enough sense
>> > > >> out of the template code in
>> ConcurrentHashtable to see why we have to
>> > > >> have it, but I'm concerned that its very
>> existence means we perhaps
>> > > >> should not be trying to extend CHT in this
>> context. ??
>> > > >
>> > > > My understanding is that is_dead parameter
>> provides a mechanism for
>> > > > ConcurrentHashtable to remove stale entries
>> that were not explicitly
>> > > > removed by calling
>> ConcurrentHashTable::remove() method.
>> > > > I think that just because in our case we
>> don't use this mechanism doesn't
>> > > > mean we should not use ConcurrentHashTable.
>> > >
>> > > Can you confirm that this usage is okay with
>> Robbin Ehn please. He's
>> > > back from vacation this week.
>> > >
>> > > >> I would still want to see what impact this
>> has on thread
>> > > >> startup cost, both with and without the
>> table being initialized.
>> > > >
>> > > > I run a test that initializes the table by
>> calling ThreadMXBean.get getThreadInfo(),
>> > > > starts some threads as a worm-up, and then
>> creates and starts 100,000 threads
>> > > > (each thread just sleeps for 100 ms). In case
>> when the thread table is enabled
>> > > > 100,000 threads are created and started for
>> about 15200 ms. If the thread table
>> > > > is off the test takes about 14800 ms. Based
>> on this information the enabled
>> > > > thread table makes the thread startup about
>> 2.7% slower.
>> > >
>> > > That doesn't sound very good. I think we may
>> need to Claes involved to
>> > > help investigate overall performance impact here.
>> > >
>> > > > Webrev:
>> https://cr.openjdk.java.net/~dtitov/8185005/webrev.04/
>> > > > Bug:
>> https://bugs.openjdk.java.net/browse/JDK-8185005
>> > >
>> > > No further code comments.
>> > >
>> > > I didn't look at the test in detail.
>> > >
>> > > Thanks,
>> > > David
>> > >
>> > > > Thanks!
>> > > > --Daniil
>> > > >
>> > > >
>> > > > On 7/29/19, 12:53 AM, "David Holmes"
>> mailto:david.holmes at oracle.com wrote:
>> > > >
>> > > > Hi Daniil,
>> > > >
>> > > > Overall I think this is a reasonable
>> approach but I would still like to
>> > > > see some performance and footprint
>> numbers, both to verify it fixes the
>> > > > problem reported, and that we are not
>> getting penalized elsewhere.
>> > > >
>> > > > On 25/07/2019 3:21 am, Daniil Titov wrote:
>> > > > > Hi David, Daniel, and Serguei,
>> > > > >
>> > > > > Please review the new version of the
>> fix, that makes the thread table initialization on demand and
>> > > > > moves it inside
>> ThreadsList::find_JavaThread_from_java_tid(). At the creation time the
>> thread table
>> > > > > is initialized with the threads from
>> the current thread list. We don't want to hold Threads_lock
>> > > > > inside
>> find_JavaThread_from_java_tid(), thus new threads still could be
>> created while the thread
>> > > > > table is being initialized . Such
>> threads will be found by the linear search and added to the thread table
>> > > > > later, in
>> ThreadsList::find_JavaThread_from_java_tid().
>> > > >
>> > > > The initialization allows the created
>> but unpopulated, or partially
>> > > > populated, table to be seen by other
>> threads - is that your intention?
>> > > > It seems it should be okay as the other
>> threads will then race with the
>> > > > initializing thread to add specific
>> entries, and this is a concurrent
>> > > > map so that should be functionally
>> correct. But if so then I think you
>> > > > can also reduce the scope of the
>> ThreadTableCreate_lock so that it
>> > > > covers creation of the table only, not
>> the initial population of the table.
>> > > >
>> > > > I like the approach of only initializing
>> the table when needed and using
>> > > > that to control when the
>> add/remove-thread code needs to update the
>> > > > table. But I would still want to see
>> what impact this has on thread
>> > > > startup cost, both with and without the
>> table being initialized.
>> > > >
>> > > > > The change also includes additional
>> optimization for some callers of find_JavaThread_from_java_tid()
>> > > > > as Daniel suggested.
>> > > >
>> > > > Not sure it's best to combine these, but
>> if they are limited to the
>> > > > changes in management.cpp only then that
>> may be okay. It helps to be
>> > > > able to focus on the table related
>> changes without being distracted by
>> > > > other optimizations.
>> > > >
>> > > > > That is correct that
>> ResolvedMethodTable was used as a blueprint for the thread table,
>> however, I tried
>> > > > > to strip it of the all functionality
>> that is not required in the thread table case.
>> > > >
>> > > > The revised version seems better in that
>> regard. But I still have a
>> > > > concern, see below.
>> > > >
>> > > > > We need to have the thread table
>> resizable and allow it to grow as the number of threads increases to
>> avoid
>> > > > > reserving excessive memory a-priori or
>> deteriorating lookup times. The ServiceThread is responsible for
>> > > > > growing the thread table when required.
>> > > >
>> > > > Yes but why? Why can't this table be
>> grown on demand by the thread that
>> > > > is doing the addition? For other tables
>> we may have to delegate to the
>> > > > service thread because the current
>> thread cannot perform the action, or
>> > > > it doesn't want to perform it at the
>> time the need for the resize is
>> > > > detected (e.g. its detected at a
>> safepoint and you want the resize to
>> > > > happen later outside the safepoint).
>> It's not apparent to me that such
>> > > > restrictions apply here.
>> > > >
>> > > > > There is no ConcurrentHashTable
>> available in Java 8 and for backporting this fix to Java 8 another
>> implementation
>> > > > > of the hash table, probably originally
>> suggested in the patch attached to the JBS issue, should be used. It
>> will make
>> > > > > the backporting more complicated,
>> however, adding a new Implementation of the hash table in Java 14
>> while it
>> > > > > already has ConcurrentHashTable
>> doesn't seem reasonable for me.
>> > > >
>> > > > Ok.
>> > > >
>> > > > > Webrev:
>> http://cr.openjdk.java.net/~dtitov/8185005/webrev.03
>> > > >
>> > > > Some specific code comments:
>> > > >
>> > > > src/hotspot/share/runtime/mutexLocker.cpp
>> > > >
>> > > > + def(ThreadTableCreate_lock ,
>> PaddedMutex , special,
>> > > > false, Monitor::_safepoint_check_never);
>> > > >
>> > > > I think this needs to be a
>> _safepoint_check_always lock. The table will
>> > > > be created by regular JavaThreads and
>> they should (nearly) always be
>> > > > checking for safepoints if they are
>> going to block acquiring the lock.
>> > > > And it isn't at all obvious that the
>> thread doing the creation can't go
>> > > > to a safepoint whilst this lock is held.
>> > > >
>> > > > ---
>> > > >
>> > > > src/hotspot/share/runtime/threadSMR.cpp
>> > > >
>> > > > Nit:
>> > > >
>> > > > 618 JavaThread* thread =
>> thread_at(i);
>> > > >
>> > > > you could reuse the new java_thread
>> local you introduced at line 613 and
>> > > > just rename that "new" variable to
>> "thread" so you don't have to change
>> > > > all other uses.
>> > > >
>> > > > 628 } else if (java_thread != NULL && ...
>> > > >
>> > > > You don't need to check != NULL here as
>> you only get here when
>> > > > java_thread is not NULL.
>> > > >
>> > > > 755 jlong tid =
>> SharedRuntime::get_java_tid(thread);
>> > > > 926 jlong tid =
>> SharedRuntime::get_java_tid(thread);
>> > > >
>> > > > I think it cleaner/better to just use
>> > > >
>> > > > jlong tid =
>> java_lang_Thread::thread_id(thread->threadObj());
>> > > >
>> > > > as we know thread is not NULL, it is a
>> JavaThread and it has to have a
>> > > > non-null threadObj.
>> > > >
>> > > > ---
>> > > >
>> > > > src/hotspot/share/services/management.cpp
>> > > >
>> > > > 1323 if
>> (THREAD->is_Java_thread()) {
>> > > > 1324 JavaThread*
>> current_thread = (JavaThread*)THREAD;
>> > > >
>> > > > These calls can only be made on a
>> JavaThread so this be simplified to
>> > > > remove the is_Java_thread() call.
>> Similarly in other places.
>> > > >
>> > > > ---
>> > > >
>> > > > src/hotspot/share/services/threadTable.cpp
>> > > >
>> > > > 55 class ThreadTableEntry : public
>> CHeapObj<mtInternal> {
>> > > > 56 private:
>> > > > 57 jlong _tid;
>> > > >
>> > > > I believe hotspot style is to not indent
>> the access modifiers in C++
>> > > > class declarations, so the above would
>> just be:
>> > > >
>> > > > 55 class ThreadTableEntry : public
>> CHeapObj<mtInternal> {
>> > > > 56 private:
>> > > > 57 jlong _tid;
>> > > >
>> > > > etc.
>> > > >
>> > > > 60 ThreadTableEntry(jlong tid,
>> JavaThread* java_thread) :
>> > > > 61
>> _tid(tid),_java_thread(java_thread) {}
>> > > >
>> > > > line 61 should be indented as it
>> continues line 60.
>> > > >
>> > > > 67 class ThreadTableConfig : public
>> AllStatic {
>> > > > ...
>> > > > 71 static uintx get_hash(Value
>> const& value, bool* is_dead) {
>> > > >
>> > > > The is_dead parameter still bothers me
>> here. I can't make enough sense
>> > > > out of the template code in
>> ConcurrentHashtable to see why we have to
>> > > > have it, but I'm concerned that its very
>> existence means we perhaps
>> > > > should not be trying to extend CHT in
>> this context. ??
>> > > >
>> > > > 115 size_t start_size_log = size_log
>> > DefaultThreadTableSizeLog
>> > > > 116 ? size_log :
>> DefaultThreadTableSizeLog;
>> > > >
>> > > > line 116 should be indented, though in
>> this case I think a better layout
>> > > > would be:
>> > > >
>> > > > 115 size_t start_size_log =
>> > > > 116 size_log >
>> DefaultThreadTableSizeLog ? size_log :
>> > > > DefaultThreadTableSizeLog;
>> > > >
>> > > > 131 double
>> ThreadTable::get_load_factor() {
>> > > > 132 return
>> (double)_items_count/_current_size;
>> > > > 133 }
>> > > >
>> > > > Not sure that is doing what you
>> want/expect. It will perform integer
>> > > > division and then cast that whole
>> integer to a double. If you want
>> > > > double arithmetic you need:
>> > > >
>> > > > return
>> ((double)_items_count)/_current_size;
>> > > >
>> > > > 180 jlong _tid;
>> > > > 181 uintx _hash;
>> > > >
>> > > > Nit: no need for all those spaces before
>> the variable name.
>> > > >
>> > > > 183 ThreadTableLookup(jlong tid)
>> > > > 184 : _tid(tid),
>> _hash(primitive_hash(tid)) {}
>> > > >
>> > > > line 184 should be indented.
>> > > >
>> > > > 201 ThreadGet():_return(NULL) {}
>> > > >
>> > > > Nit: need space after :
>> > > >
>> > > > 211 assert(_is_initialized, "Thread
>> table is not initialized");
>> > > > 212 _has_work = false;
>> > > >
>> > > > line 211 is indented one space too far.
>> > > >
>> > > > 229 ThreadTableEntry* entry = new
>> ThreadTableEntry(tid,java_thread);
>> > > >
>> > > > Nit: need space after ,
>> > > >
>> > > > 252 return
>> _local_table->remove(thread,lookup);
>> > > >
>> > > > Nit: need space after ,
>> > > >
>> > > > Thanks,
>> > > > David
>> > > > ------
>> > > >
>> > > > > Bug:
>> https://bugs.openjdk.java.net/browse/JDK-8185005
>> > > > >
>> > > > > Thanks!
>> > > > > --Daniil
>> > > > >
>> > > > >
>> > > > > On 7/8/19, 3:24 PM, "Daniel D.
>> Daugherty" mailto:daniel.daugherty at oracle.com wrote:
>> > > > >
>> > > > > On 6/29/19 12:06 PM, Daniil Titov
>> wrote:
>> > > > > > Hi Serguei and David,
>> > > > > >
>> > > > > > Serguei is right,
>> ThreadTable::find_thread(java_tid) cannot return a JavaThread with an
>> unmatched java_tid.
>> > > > > >
>> > > > > > Please find a new version of
>> the fix that includes the changes Serguei suggested.
>> > > > > >
>> > > > > > Regarding the concern about the
>> maintaining the thread table when it may never even be queried, one of
>> > > > > > the options could be to add
>> ThreadTable ::isEnabled flag, set it to "false" by default, and wrap
>> the calls to the thread table
>> > > > > > in ThreadsSMRSupport
>> add_thread() and remove_thread() methods to check this flag.
>> > > > > >
>> > > > > > When
>> ThreadsList::find_JavaThread_from_java_tid() is called for the first
>> time it could check if ThreadTable ::isEnabled
>> > > > > > Is on and if not then set it on
>> and populate the thread table with all existing threads from the
>> thread list.
>> > > > >
>> > > > > I have the same concerns as David
>> H. about this new ThreadTable.
>> > > > >
>> ThreadsList::find_JavaThread_from_java_tid() is only called from code
>> > > > > in
>> src/hotspot/share/services/management.cpp so I think that table
>> > > > > needs to enabled and populated
>> only if it is going to be used.
>> > > > >
>> > > > > I've taken a look at the webrev
>> below and I see that David has
>> > > > > followed up with additional
>> comments. Before I do a crawl through
>> > > > > code review for this, I would
>> like to see the ThreadTable stuff
>> > > > > made optional and David's other
>> comments addressed.
>> > > > >
>> > > > > Another possible optimization is
>> for callers of
>> > > > > find_JavaThread_from_java_tid()
>> to save the calling thread's
>> > > > > tid value before they loop and if
>> the current tid == saved_tid
>> > > > > then use the current JavaThread*
>> instead of calling
>> > > > > find_JavaThread_from_java_tid()
>> to get the JavaThread*.
>> > > > >
>> > > > > Dan
>> > > > >
>> > > > > >
>> > > > > > Webrev:
>> https://cr.openjdk.java.net/~dtitov/8185005/webrev.02/
>> > > > > > Bug:
>> https://bugs.openjdk.java.net/browse/JDK-8185005
>> > > > > >
>> > > > > > Thanks!
>> > > > > > --Daniil
>> > > > > >
>> > > > > > From:
>> mailto:serguei.spitsyn at oracle.com
>> > > > > > Organization: Oracle Corporation
>> > > > > > Date: Friday, June 28, 2019 at
>> 7:56 PM
>> > > > > > To: Daniil Titov
>> mailto:daniil.x.titov at oracle.com, OpenJDK Serviceability
>> mailto:serviceability-dev at openjdk.java.net,
>> mailto:hotspot-runtime-dev at openjdk.java.net
>> mailto:hotspot-runtime-dev at openjdk.java.net,
>> mailto:jmx-dev at openjdk.java.net mailto:jmx-dev at openjdk.java.net
>> > > > > > Subject: Re: RFR: 8185005:
>> Improve performance of ThreadMXBean.getThreadInfo(long ids[], int
>> maxDepth)
>> > > > > >
>> > > > > > Hi Daniil,
>> > > > > >
>> > > > > > I have several quick comments.
>> > > > > >
>> > > > > > The indent in the hotspot c/c++
>> files has to be 2, not 4.
>> > > > > >
>> > > > > >
>> https://cr.openjdk.java.net/~dtitov/8185005/webrev.01/src/hotspot/share/runtime/threadSMR.cpp.frames.html
>>
>> > > > > > 614 JavaThread*
>> ThreadsList::find_JavaThread_from_java_tid(jlong java_tid) const {
>> > > > > > 615 JavaThread*
>> java_thread = ThreadTable::find_thread(java_tid);
>> > > > > > 616 if (java_thread ==
>> NULL && java_tid == PMIMORDIAL_JAVA_TID) {
>> > > > > > 617 //
>> ThreadsSMRSupport::add_thread() is not called for the primordial
>> > > > > > 618 // thread. Thus,
>> we find this thread with a linear search and add it
>> > > > > > 619 // to the thread
>> table.
>> > > > > > 620 for (uint i = 0;
>> i < length(); i++) {
>> > > > > > 621 JavaThread*
>> thread = thread_at(i);
>> > > > > > 622 if
>> (is_valid_java_thread(java_tid,thread)) {
>> > > > > > 623
>> ThreadTable::add_thread(java_tid, thread);
>> > > > > > 624 return
>> thread;
>> > > > > > 625 }
>> > > > > > 626 }
>> > > > > > 627 } else if
>> (java_thread != NULL && is_valid_java_thread(java_tid, java_thread)) {
>> > > > > > 628 return java_thread;
>> > > > > > 629 }
>> > > > > > 630 return NULL;
>> > > > > > 631 }
>> > > > > > 632 bool
>> ThreadsList::is_valid_java_thread(jlong java_tid, JavaThread*
>> java_thread) {
>> > > > > > 633 oop tobj =
>> java_thread->threadObj();
>> > > > > > 634 // Ignore the thread
>> if it hasn't run yet, has exited
>> > > > > > 635 // or is starting to
>> exit.
>> > > > > > 636 return (tobj != NULL
>> && !java_thread->is_exiting() &&
>> > > > > > 637 java_tid ==
>> java_lang_Thread::thread_id(tobj));
>> > > > > > 638 }
>> > > > > >
>> > > > > > 615 JavaThread*
>> java_thread = ThreadTable::find_thread(java_tid);
>> > > > > >
>> > > > > > I'd suggest to rename
>> find_thread() to find_thread_by_tid().
>> > > > > >
>> > > > > > A space is missed after the comma:
>> > > > > > 622 if
>> (is_valid_java_thread(java_tid,thread)) {
>> > > > > >
>> > > > > > An empty line is needed before
>> L632.
>> > > > > >
>> > > > > > The name 'is_valid_java_thread'
>> looks wrong (or confusing) to me.
>> > > > > > Something like
>> 'is_alive_java_thread_with_tid()' would be better.
>> > > > > > It'd better to list parameters
>> in the opposite order.
>> > > > > >
>> > > > > > The call to
>> is_valid_java_thread() is confusing:
>> > > > > > 627 } else if (java_thread
>> != NULL && is_valid_java_thread(java_tid, java_thread)) {
>> > > > > >
>> > > > > > Why would the call
>> ThreadTable::find_thread(java_tid) return a JavaThread with an
>> unmatched java_tid?
>> > > > > >
>> > > > > >
>> > > > > > Thanks,
>> > > > > > Serguei
>> > > > > >
>> > > > > > On 6/28/19, 9:40 PM, "David
>> Holmes" mailto:david.holmes at oracle.com wrote:
>> > > > > >
>> > > > > > Hi Daniil,
>> > > > > >
>> > > > > > The definition and use of
>> this hashtable (yet another hashtable
>> > > > > > implementation!) will need
>> careful examination. We have to be concerned
>> > > > > > about the cost of
>> maintaining it when it may never even be queried. You
>> > > > > > would need to look at
>> footprint cost and performance impact.
>> > > > > >
>> > > > > > Unfortunately I'm just
>> about to board a plane and will be out for the
>> > > > > > next few days. I will try
>> to look at this asap next week, but we will
>> > > > > > need a lot more data on it.
>> > > > > >
>> > > > > > Thanks,
>> > > > > > David
>> > > > > >
>> > > > > > On 6/28/19 3:31 PM, Daniil
>> Titov wrote:
>> > > > > > Please review the change that
>> improves performance of ThreadMXBean MXBean methods returning the
>> > > > > > information for specific
>> threads. The change introduces the thread table that uses
>> ConcurrentHashTable
>> > > > > > to store one-to-one the mapping
>> between the thread ids and JavaThread objects and replaces the linear
>> > > > > > search over the thread list in
>> ThreadsList::find_JavaThread_from_java_tid(jlong tid) method with the
>> lookup
>> > > > > > in the thread table.
>> > > > > >
>> > > > > > Testing: Mach5 tier1,tier2 and
>> tier3 tests successfully passed.
>> > > > > >
>> > > > > > Webrev:
>> https://cr.openjdk.java.net/~dtitov/8185005/webrev.01/
>> > > > > > Bug:
>> https://bugs.openjdk.java.net/browse/JDK-8185005
>> > > > > >
>> > > > > > Thanks!
>> > > > > >
>> > > > > > Best regards,
>> > > > > > Daniil
>> > > > > >
>> > > > > >
>> > > > > >
>> > > > > >
>> > > > > >
>> > > > > >
>> > > > > >
>> > > > >
>> > > > >
>> > > > >
>> > > > >
>> > > >
>> > > >
>> > > >
>> > >
>> > >
>> > >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>>
>>
>>
>>
>>
>>
>
More information about the serviceability-dev
mailing list