-
Notifications
You must be signed in to change notification settings - Fork 68
8273107: RunThese24H times out with "java.lang.management.ThreadInfo.getLockName()" is null #25
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,44 @@ | |
#include "utilities/events.hpp" | ||
#include "utilities/preserveException.hpp" | ||
|
||
class CleanupObjectMonitorsHashtable: StackObj { | ||
public: | ||
bool do_entry(JavaThread*& key, ObjectMonitorsHashtable::PtrList*& list) { | ||
list->clear(); // clear the LinkListNodes | ||
delete list; // then delete the LinkedList | ||
return true; | ||
} | ||
}; | ||
|
||
ObjectMonitorsHashtable::~ObjectMonitorsHashtable() { | ||
CleanupObjectMonitorsHashtable cleanup; | ||
_ptrs->unlink(&cleanup); // cleanup the LinkedLists | ||
delete _ptrs; // then delete the hash table | ||
} | ||
|
||
void ObjectMonitorsHashtable::add_entry(JavaThread* jt, ObjectMonitor* om) { | ||
ObjectMonitorsHashtable::PtrList* list = get_entry(jt); | ||
if (list != nullptr) { | ||
// Add to existing list from the hash table: | ||
list->add(om); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two lines are executed in both the if and else clause. Consider putting it after instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll take a look at doing that. Thanks for the suggestion. |
||
_om_count++; | ||
} else { | ||
// Create new list, add the ObjectMonitor* to it, and add the list to the hash table: | ||
list = new (ResourceObj::C_HEAP, mtThread) ObjectMonitorsHashtable::PtrList(); | ||
list->add(om); | ||
_om_count++; | ||
add_entry(jt, list); | ||
} | ||
} | ||
|
||
bool ObjectMonitorsHashtable::has_entry(JavaThread* jt, ObjectMonitor* om) { | ||
ObjectMonitorsHashtable::PtrList* list = get_entry(jt); | ||
if (list == nullptr || list->find(om) == nullptr) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
void MonitorList::add(ObjectMonitor* m) { | ||
ObjectMonitor* head; | ||
do { | ||
|
@@ -992,6 +1030,8 @@ JavaThread* ObjectSynchronizer::get_lock_owner(ThreadsList * t_list, Handle h_ob | |
|
||
// Visitors ... | ||
|
||
// This version of monitors_iterate() works with the in-use monitor list. | ||
// | ||
void ObjectSynchronizer::monitors_iterate(MonitorClosure* closure, JavaThread* thread) { | ||
MonitorList::Iterator iter = _in_use_list.iterator(); | ||
while (iter.has_next()) { | ||
|
@@ -1013,6 +1053,30 @@ void ObjectSynchronizer::monitors_iterate(MonitorClosure* closure, JavaThread* t | |
} | ||
} | ||
|
||
// This version of monitors_iterate() works with the specified linked list. | ||
// | ||
void ObjectSynchronizer::monitors_iterate(MonitorClosure* closure, | ||
ObjectMonitorsHashtable::PtrList* list, | ||
JavaThread* thread) { | ||
typedef LinkedListIterator<ObjectMonitor*> ObjectMonitorIterator; | ||
ObjectMonitorIterator iter = ObjectMonitorIterator(list->head()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to create an object and then copy it to iter. I think the more obvious syntax is ObjectMonitorIterator iter(list->head()); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch. I'll fix that. |
||
while (!iter.is_empty()) { | ||
ObjectMonitor* mid = *iter.next(); | ||
assert(mid->owner() == thread, "must be"); | ||
if (!mid->is_being_async_deflated() && mid->object_peek() != NULL) { | ||
// Only process with closure if the object is set. | ||
|
||
// monitors_iterate() is only called at a safepoint or when the | ||
// target thread is suspended or when the target thread is | ||
// operating on itself. The current closures in use today are | ||
// only interested in an owned ObjectMonitor and ownership | ||
// cannot be dropped under the calling contexts so the | ||
// ObjectMonitor cannot be async deflated. | ||
closure->do_monitor(mid); | ||
} | ||
} | ||
} | ||
|
||
static bool monitors_used_above_threshold(MonitorList* list) { | ||
if (MonitorUsedDeflationThreshold == 0) { // disabled case is easy | ||
return false; | ||
|
@@ -1339,7 +1403,8 @@ void ObjectSynchronizer::chk_for_block_req(JavaThread* current, const char* op_n | |
// Walk the in-use list and deflate (at most MonitorDeflationMax) idle | ||
// ObjectMonitors. Returns the number of deflated ObjectMonitors. | ||
size_t ObjectSynchronizer::deflate_monitor_list(Thread* current, LogStream* ls, | ||
elapsedTimer* timer_p) { | ||
elapsedTimer* timer_p, | ||
ObjectMonitorsHashtable* table) { | ||
MonitorList::Iterator iter = _in_use_list.iterator(); | ||
size_t deflated_count = 0; | ||
|
||
|
@@ -1350,6 +1415,15 @@ size_t ObjectSynchronizer::deflate_monitor_list(Thread* current, LogStream* ls, | |
ObjectMonitor* mid = iter.next(); | ||
if (mid->deflate_monitor()) { | ||
deflated_count++; | ||
} else if (table != nullptr) { | ||
// The caller is interested in the owned ObjectMonitors. This does not capture | ||
// unowned ObjectMonitors that cannot be deflated because of a waiter. Since | ||
// deflate_idle_monitors() and deflate_monitor_list() can be called more than | ||
// once, we have to make sure the entry has not already been added. | ||
JavaThread* jt = (JavaThread*)mid->owner(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmm. I'm not sure this is correct. The owned of an ObjectMonitor is a JavaThread most of the time, but not always. I think that if the owner started off with a stack lock, and another thread inflated the lock due to contention, then the ObjectMonitor will get the stack lock as the owner, as the random other thread is too lazy to look up which thread stack the stack lock belongs to. Because of that, I think we might miss locks that really are owned by a thread, as the thread key now will not map to such locks. Some kind of ownership lookup probably needs to happen here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch on an oddity of ObjectMonitor ownership! The baseline code has the same "issue":
Please notice that I said "issue" and not "bug". This isn't a bug and you really Because of this ownership check Here's the closure:
This particular monitors_iterate() call uses InflatedMonitorsClosure to I definitely need to clarify the baseline comments and the new code |
||
if (jt != nullptr && !table->has_entry(jt, mid)) { | ||
table->add_entry(jt, mid); | ||
} | ||
} | ||
|
||
if (current->is_Java_thread()) { | ||
|
@@ -1374,8 +1448,8 @@ class HandshakeForDeflation : public HandshakeClosure { | |
|
||
// This function is called by the MonitorDeflationThread to deflate | ||
// ObjectMonitors. It is also called via do_final_audit_and_print_stats() | ||
// by the VMThread. | ||
size_t ObjectSynchronizer::deflate_idle_monitors() { | ||
// and VM_ThreadDump::doit() by the VMThread. | ||
size_t ObjectSynchronizer::deflate_idle_monitors(ObjectMonitorsHashtable* table) { | ||
Thread* current = Thread::current(); | ||
if (current->is_Java_thread()) { | ||
// The async deflation request has been processed. | ||
|
@@ -1400,7 +1474,7 @@ size_t ObjectSynchronizer::deflate_idle_monitors() { | |
} | ||
|
||
// Deflate some idle ObjectMonitors. | ||
size_t deflated_count = deflate_monitor_list(current, ls, &timer); | ||
size_t deflated_count = deflate_monitor_list(current, ls, &timer, table); | ||
if (deflated_count > 0 || is_final_audit()) { | ||
// There are ObjectMonitors that have been deflated or this is the | ||
// final audit and all the remaining ObjectMonitors have been | ||
|
@@ -1458,6 +1532,10 @@ size_t ObjectSynchronizer::deflate_idle_monitors() { | |
} | ||
ls->print_cr("end deflating: in_use_list stats: ceiling=" SIZE_FORMAT ", count=" SIZE_FORMAT ", max=" SIZE_FORMAT, | ||
in_use_list_ceiling(), _in_use_list.count(), _in_use_list.max()); | ||
if (table != nullptr) { | ||
ls->print_cr("ObjectMonitorsHashtable: jt_count=" SIZE_FORMAT ", om_count=" SIZE_FORMAT, | ||
table->jt_count(), table->om_count()); | ||
} | ||
} | ||
|
||
OM_PERFDATA_OP(MonExtant, set_value(_in_use_list.count())); | ||
|
@@ -1560,7 +1638,7 @@ void ObjectSynchronizer::do_final_audit_and_print_stats() { | |
// Do a deflation in order to reduce the in-use monitor population | ||
// that is reported by ObjectSynchronizer::log_in_use_monitor_details() | ||
// which is called by ObjectSynchronizer::audit_and_print_stats(). | ||
while (ObjectSynchronizer::deflate_idle_monitors() != 0) { | ||
while (ObjectSynchronizer::deflate_idle_monitors(/* ObjectMonitorsHashtable is not needed here */ nullptr) >= (size_t)MonitorDeflationMax) { | ||
; // empty | ||
} | ||
// The other audit_and_print_stats() call is done at the Debug | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -279,6 +279,18 @@ void VM_ThreadDump::doit() { | |
concurrent_locks.dump_at_safepoint(); | ||
} | ||
|
||
ObjectMonitorsHashtable table; | ||
ObjectMonitorsHashtable* tablep = nullptr; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like you can remove this pointer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now From this call site, when For other |
||
if (_with_locked_monitors) { | ||
// The caller wants locked monitor information and that's expensive to gather | ||
// when there are a lot of inflated monitors. So we deflate idle monitors and | ||
// gather information about owned monitors at the same time. | ||
tablep = &table; | ||
while (ObjectSynchronizer::deflate_idle_monitors(tablep) >= (size_t)MonitorDeflationMax) { | ||
; /* empty */ | ||
} | ||
} | ||
|
||
if (_num_threads == 0) { | ||
// Snapshot all live threads | ||
|
||
|
@@ -293,7 +305,7 @@ void VM_ThreadDump::doit() { | |
if (_with_locked_synchronizers) { | ||
tcl = concurrent_locks.thread_concurrent_locks(jt); | ||
} | ||
snapshot_thread(jt, tcl); | ||
snapshot_thread(jt, tcl, tablep); | ||
} | ||
} else { | ||
// Snapshot threads in the given _threads array | ||
|
@@ -328,14 +340,15 @@ void VM_ThreadDump::doit() { | |
if (_with_locked_synchronizers) { | ||
tcl = concurrent_locks.thread_concurrent_locks(jt); | ||
} | ||
snapshot_thread(jt, tcl); | ||
snapshot_thread(jt, tcl, tablep); | ||
} | ||
} | ||
} | ||
|
||
void VM_ThreadDump::snapshot_thread(JavaThread* java_thread, ThreadConcurrentLocks* tcl) { | ||
void VM_ThreadDump::snapshot_thread(JavaThread* java_thread, ThreadConcurrentLocks* tcl, | ||
ObjectMonitorsHashtable* table) { | ||
ThreadSnapshot* snapshot = _result->add_thread_snapshot(java_thread); | ||
snapshot->dump_stack_at_safepoint(_max_depth, _with_locked_monitors); | ||
snapshot->dump_stack_at_safepoint(_max_depth, _with_locked_monitors, table); | ||
snapshot->set_concurrent_locks(tcl); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -659,7 +659,7 @@ ThreadStackTrace::~ThreadStackTrace() { | |
} | ||
} | ||
|
||
void ThreadStackTrace::dump_stack_at_safepoint(int maxDepth) { | ||
void ThreadStackTrace::dump_stack_at_safepoint(int maxDepth, ObjectMonitorsHashtable* table) { | ||
assert(SafepointSynchronize::is_at_safepoint(), "all threads are stopped"); | ||
|
||
if (_thread->has_last_Java_frame()) { | ||
|
@@ -683,9 +683,17 @@ void ThreadStackTrace::dump_stack_at_safepoint(int maxDepth) { | |
|
||
if (_with_locked_monitors) { | ||
// Iterate inflated monitors and find monitors locked by this thread | ||
// not found in the stack | ||
// and not found in the stack: | ||
InflatedMonitorsClosure imc(this); | ||
ObjectSynchronizer::monitors_iterate(&imc, _thread); | ||
if (table != nullptr) { | ||
// Get the ObjectMonitors locked by this thread (if any): | ||
ObjectMonitorsHashtable::PtrList* list = table->get_entry(_thread); | ||
if (list != nullptr) { | ||
ObjectSynchronizer::monitors_iterate(&imc, list, _thread); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The InflatedMonitorsClosure walks the stack until object is found with this method: If you instead just collect all locks on stack with one pass you don't have to walk the stack over and over, which should be major speedup. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like an interesting RFE, but I'd prefer that be investigated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarification: ThreadStackTrace::is_owned_monitor_on_stack() is not actually There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, sorry, we loop the stack frame info for every object. I.e. a loop in a loop which is bad for time-complexity. |
||
} | ||
} else { | ||
ObjectSynchronizer::monitors_iterate(&imc, _thread); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -936,9 +944,10 @@ ThreadSnapshot::~ThreadSnapshot() { | |
delete _concurrent_locks; | ||
} | ||
|
||
void ThreadSnapshot::dump_stack_at_safepoint(int max_depth, bool with_locked_monitors) { | ||
void ThreadSnapshot::dump_stack_at_safepoint(int max_depth, bool with_locked_monitors, | ||
ObjectMonitorsHashtable* table) { | ||
_stack_trace = new ThreadStackTrace(_thread, with_locked_monitors); | ||
_stack_trace->dump_stack_at_safepoint(max_depth); | ||
_stack_trace->dump_stack_at_safepoint(max_depth, table); | ||
} | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this being called by anything ???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See L71 below: _ptrs->unlink(&cleanup); // cleanup the LinkedLists
That passes the cleanup object to ResourceHashtable::unlink()
function which calls the do_entry() function in the cleanup object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see (well I don't really see as this is very obscure). I would have expected
CleanupObjectMonitorsHashtable
to be a subtype of something that I could then see is used by the ResourceHashtable. I'm going to guess that there is some template usage here that ensures the passed in object has the right API even if there is no subtyping relationship. Hard to see these connections at a distance.To be clear not a review as I don't understand our Hashtable's enough to make much sense of what is actually happening here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be a nice place for a lambda (on the list of future RFEs). You could also put this class in scope of the function that calls it which might help. (optional)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ResourceHashtable::unlink() is definitely one of those template
things that I don't really like, but I have to use it... All that stuff
is in src/hotspot/share/utilities/resourceHash.hpp, but I don't
see any precedent for putting the CleanupObjectMonitorsHashtable
definition in there. I think I'll leave it where it is.