- Loading...
...
RFE: 8153224 Monitor deflation prolong safepoints
https://bugs.openjdk.java.net/browse/JDK-8153224
Full Webrev: 1317-for-jdk15+1124.v2.1015.full
Inc Webrev: 1317-for-jdk15+1124.v2.1015.inc
This patch for Async Monitor Deflation is based on Carsten Varming's
...
The current idle monitor deflation mechanism executes at a safepoint during cleanup operations. Due to this execution environment, the current mechanism does not have to worry about interference from concurrently executing JavaThreads. Async Monitor Deflation uses the ServiceThread to deflate idle monitors so the new mechanism has to detect interference and adapt as appropriate. In other words, data races are natural part of Async Monitor Deflation and the algorithms have to detect the races and react without data loss or corruption.
Async Monitor Deflation is performed in two stages: stage one performs the two part protocol described in "Deflation With Interference Detection" below and moves the async deflated ObjectMonitors from an in-use list to a global wait list; the ServiceThread performs a handshake (or a safepoint) with all other JavaThreads after stage one is complete and that forces any racing threads to make forward progress; stage two moves the ObjectMonitors from the global wait list to the global free list. The special values that mark an ObjectMonitor as async deflated remain in their fields until the ObjectMonitor is moved from the global free list to a per-thread free list which is sometime after stage two has completed.
...
ObjectSynchronizer::deflate_monitor_using_JT() is the new counterpart to ObjectSynchronizer::deflate_monitor() and does the heavy lifting of asynchronously deflating a monitor using a three two part prototcol:
If
...
If we lose any of the races, the we lose any of the races, the monitor cannot be deflated at this time.
Once we know it is safe to deflate the monitor (which is mostly field resetting and monitor list management), we have to restore the object's header. That's another racy operation that is described below in "Restoring the Header With Interference Detection".
The setting of the special values that mark an ObjectMonitor as async deflated and the restoration of the object's header comprise the first stage of Async Monitor Deflation.
ObjectMonitor::install_displaced_markword_in_object() is the new piece of code that handles all the racy situations with restoring an object's header asynchronously. The function is called from two three places (deflation and saving an ObjectMonitor* in an ObjectMonitorHandle, ObjectMonitor::enter(), and FastHashCode). Only one of the possible racing scenarios can win and the losing scenarios all adapt to the winning scenario's object header value.
...
Various code paths have been updated to recognize an owner field equal to DEFLATER_MARKER or a negative ref_count contentions field and those code paths will retry their operation. This is the shortest "Key Part" description, but don't be fooled. See "Gory Details" below.
ObjectMonitor::save_om_ptrenter() is used to safely save an ObjectMonitor* in an ObjectMonitorHandlecan change an idle monitor into a busy monitor. ObjectSynchronizer::deflate_monitor_using_JT() is used to asynchronously deflate an idle monitor. save_om_ptrenter() and deflate_monitor_using_JT() can interfere with each other. The thread calling save_om_ptrenter() (T-saveenter) is potentially racing with another JavaThread (T-deflate) so both threads have to check the results of the races.
T-save enter ObjectMonitor T-deflate
------------------------ +-----------------------+ ----------------------------------------
save_om_ptrenter() { | owner=NULL | deflate_monitor_using_JT() {
1> atomic inc ref_countcontentions | ref_countcontentions=0 | 1> cmpxchg(DEFLATER_MARKER, &owner, NULL)
+-----------------------+
T-save enter ObjectMonitor T-deflate
------------------------ +-----------------------+ --------------------------------------------
save_om_ptrenter() { | owner=DEFLATER_MARKER | | owner=DEFLATER_MARKER | deflate_monitor_using_JT() {
1> atomic inc ref_count | ref_countadd_to_contentions(1) | contentions=0 | cmpxchg(try_set_owner_from(NULL, DEFLATER_MARKER, &owner, NULL)
+-----------------------+ :
1> prev = cmpxchg(&contentions, 0, -max_jint, &ref_count, 0)
T-save enter ObjectMonitor ObjectMonitor T-deflate
---------------------------------- +-------------------------+ --------------------------------------------
save_om_ptrenter() { | owner=DEFLATER_MARKER | deflate_monitor_using_JT() {
atomic inc ref_countadd_to_contentions(1) | ref_countcontentions=-max_jint+1 | cmpxchg(try_set_owner_from(NULL, DEFLATER_MARKER, &owner, NULL)
1> if (owner == DEFLATER_MARKER &&is_being_async_deflated()) { +-------------------------+ :
restore obj ref_countheader <= 0) { || prev = cmpxchg(&contentions, 0, -max_jint, &ref_count, 0)
restore obj headeradd_to_contentions(-1) \/ 1> if (prev == 0) &&
atomic dec ref_count {
2> return false to force retry +-------------------------+ owner == DEFLATER_MARKER) {
2> return false to force retry restore obj header
} | owner=DEFLATER_MARKER | 2> finish restorethe obj headerdeflation
} | ref_countcontentions=-max_jint | 2> finish the deflation}
+-------------------------+ }
...
T-save enter ObjectMonitor T-deflate
---------------------------------- +-------------------------+ ---------------------------------------------
save_om_ptrenter() { | owner=DEFLATER_MARKER. | deflate_monitor_using_JT() {
atomic inc ref_count add_to_contentions(1) | ref_countcontentions=1 | cmpxchg(try_set_owner_from(NULL, DEFLATER_MARKER, &owner, NULL)
1> if (owner == DEFLATER_MARKER &&is_being_async_deflated()) { +-------------------------+ :
} ref_count <= 0) { || prev = cmpxchg(-max_jint, &ref_count, 0)
} else { || prev = cmpxchg(&contentions, 0, -max_jint)
2> <continue contended enter> \/ 1> if (prev == 0) &&
{
save om_ptr in the +-- +-------------------------+ owner == DEFLATER_MARKER)} else {
ObjectMonitorHandle | owner=NULL | } else {
try_set_owner_from(DEFLATER_MARKER, NULL)
2> return true | ref_countcontentions=1 | cmpxchg(NULL, &owner, DEFLATER_MARKER)2> return
+-----------------------+ 2> return--+
...
T-enter ObjectMonitor T-deflate
-------------------------------------------- +-------------------------+ --------------------------------------------
ObjectMonitor::enter() { | owner=DEFLATER_MARKER | deflate_monitor_using_JT() {
<owner is contended> add_to_contentions(1) | ref_countcontentions=1 | cmpxchg(try_set_owner_from(NULL, DEFLATER_MARKER, &owner, NULL)
1> EnterI() { +-------------------------+ 1> :
if (owner == try_set_owner_from(DEFLATER_MARKER && , || 2> : <thread_stalls>
cmpxchg(Self, &owner,Self) == DEFLATER_MARKER) { \/ :
// Add marker for cancellation DEFLATER_MARKER) +-------------------------+ :
add_to_contentions(1) == DEFLATER_MARKER) { | owner=Self/T-enter | :
// EnterI is done | ref_countcontentions=02 | : <thread_resumes>
return +-------------------------+ prev = cmpxchg(&contentions, 0, -max_jint, &ref_count, 0)
} || if (prev == 0) &&{
2> } // enter( add_to_contentions(-1) is done \/ \/ 3> } else {
} 3> owner == DEFLATER_MARKER) {
// enter() is done ~OMH: atomic dec ref_count +-------------------------+ } else {
2>if (try_set_owner_from(DEFLATER_MARKER,
: <does app work> | owner=Self/T-enter|NULL | cmpxchg(NULL, &owner, NULL) != DEFLATER_MARKER) {
3> : | ref_count=-max_jintcontentions=1 | atomic add max_jint to ref_count_contentions(-1)
exit() monitor +-------------------------+ 4> bailout on deflation}
4> owner = NULL || 4> bailout on }deflation
\/ }
+-------------------------+
| owner=Self/T-enter|NULL |
| ref_countcontentions=0 |
+-------------------------+
T-enter finished doing app work and is about to exit the monitor (or it has already exited the monitor).
performs the A-B-A check which observes that "owner != DEFLATER_MARKER" and bails out on deflation:NULL → DEFLATER_MARKER → Self/T-enter
so we really have A1-B-A2, but the A-B-A principal still holds.
If the T-enter thread has managed to enter and exit the monitor during the T-deflate stall, then our owner field A-B-A transition is:
NULL → DEFLATER_MARKER → Self/T-enter → NULL
so we really have A-B1-B2-A, but the A-B-A principal still holds.
T-enter finished doing app work and is about to exit the monitor (or it has already exited the monitor).
The fourth ObjectMonitor box is showing the fields at this point and the "4>" markers are showing where each thread is at for that ObjectMonitor box.
...
ObjectMonitor::install_displaced_markword_in_object() is called from two places so we can have a race between a T-save enter thread and a T-deflate thread:
T-save object T-deflate
----------------------------------------------- +-------------+ -----------------------------------------------
install_displaced_markword_in_object(oop obj) { | mark=om_ptr | install_displaced_markword_in_object(oop obj) {
dmw = header() +-------------+ dmw = header()
obj->cas_set_mark(dmw, this) obj->cas_set_mark(dmw, this) }
T-save object T-deflate
----------------------------------------------- +-------------+ -----------------------------------------------
install_displaced_markword_in_object(oop obj) { | mark=dmw | install_displaced_markword_in_object(oop obj) {
dmw = header() +-------------+ dmw = header()
obj->cas_set_mark(dmw, this) obj->cas_set_mark(dmw, this)
Please notice that install_displaced_markword_in_object() does not do any retries on any code path:
If we have a race between a T-deflate thread and a thread trying to get/set a hashcode (T-hash), then the race is between the ObjectMonitorHandle.save_om_ptr(obj, mark) call in T-hash and deflation protocol in T-deflate.
T-hash ObjectMonitor T-deflate
---------------------- +-----------------------+ ----------------------------------------
save_om_ptr() { | owner=NULL | deflate_monitor_using_JT() {
: | ref_count=0 | 1> cmpxchg(DEFLATER_MARKER, &owner, NULL)
1> atomic inc ref_count +-----------------------+
T-hash ObjectMonitor T-deflate
---------------------- +-----------------------+ ------------------------------------------
save_om_ptr() { | owner=DEFLATER_MARKER | deflate_monitor_using_JT() {
: | ref_count=0 | cmpxchg(DEFLATER_MARKER, &owner, NULL)
1> atomic inc ref_count +-----------------------+ if (contentions != 0 || waiters != 0) {
}
1> prev = cmpxchg(-max_jint, &ref_count, 0)
If T-deflate wins the race, then T-hash will have to retry at most once.
T-hash ObjectMonitor T-deflate
------------------------- +-----------------------+ ------------------------------------------
save_om_ptr() { | owner=DEFLATER_MARKER | deflate_monitor_using_JT() {
1> atomic inc ref_count | ref_count=-max_jint | cmpxchg(DEFLATER_MARKER, &owner, NULL)
if (owner == +-----------------------+ if (contentions != 0 || waiters != 0) {
DEFLATER_MARKER && || }
ref_count <= 0) { \/ prev = cmpxchg(-max_jint, &ref_count, 0)
restore obj header +-----------------------+ 1> if (prev == 0 &&
atomic dec ref_count | owner=DEFLATER_MARKER | owner == DEFLATER_MARKER) {
2> return false to | ref_count=-max_jint | restore obj header
cause a retry +-----------------------+ 2> finish the deflation
} }
If T-hash wins the race, then the ref_count will cause T-deflate to bail out on deflating the monitor.
Note: header is not mentioned in any of the previous sections for simplicity.
T-hash ObjectMonitor T-deflate
------------------------- +-----------------------+ ------------------------------------------
save_om_ptr() { | header=dmw_no_hash | deflate_monitor_using_JT() {
atomic inc ref_count | owner=DEFLATER_MARKER | cmpxchg(DEFLATER_MARKER, &owner, NULL)
1> if (owner == | ref_count=1 | if (contentions != 0 || waiters != 0) {
DEFLATER_MARKER && +-----------------------+ }
ref_count <= 0) { || 1> prev = cmpxchg(-max_jint, &ref_count, 0)
} else { \/ if (prev == 0 &&
2> save om_ptr in the +enter object T-deflate
----------------------------------------------- +-------------+ owner == DEFLATER_MARKER) {
ObjectMonitorHandle | header=dmw_no_hash | } else {
return true | owner=NULL | cmpxchg(NULL, &owner, DEFLATER_MARKER)
} | ref_count=1 | 2> bailout on deflation
} -----------------------------------------------
install_displaced_markword_in_object(oop obj) { | mark=om_ptr | install_displaced_markword_in_object(oop obj) {
dmw = header() +-------------+ dmw = header()
obj----------+>cas_set_mark(dmw, this) obj->cas_set_mark(dmw, this) }
if save_om_ptr() { ||
if no hash \/
gen hash & merge +}
T-enter object T-deflate
----------------------------------------------- +-------------+ -----------------------------------------------+
hash = hash(header) | header=dmw_hash |
} | owner=NULL |
3> atomic dec ref_count | ref_count=1 |
return hash install_displaced_markword_in_object(oop obj) { | mark=dmw | install_displaced_markword_in_object(oop obj) {
dmw = header() +-------------+ dmw = header()
obj->cas_set_mark(dmw, this) obj->cas_set_mark(dmw, this)
...
Please notice that install_displaced_markword_in_object() does not do any retries on any code path:
There are a few races that can occur between a T-deflate thread and a thread trying to get/set a hashcode (T-hash) in an ObjectMonitor:
The common fall thru code path (executed by T-hash) that inflates the ObjectMonitor in order to set the hashcode can race with an async deflation (T-deflate). After the hashcode has been stored in the ObjectMonitor, we (T-hash) check if the ObjectMonitor has been async deflated (by T-deflate). If it has, then we (T-hash) retry because we don't know if the hashcode was stored in the ObjectMonitor before the object's header was restored (by T-deflate). Retrying (by T-hash) will result in the hashcode being stored in either object's header or in the re-inflated ObjectMonitor's header as appropriate.
...
...
ObjectSynchronizer::deflate_monitor_list_using_JT() is responsible for asynchronously deflating idle ObjectMonitors using a JavaThread. This function uses the more complicated lock-cur_mid_in_use-and-mid-as-we-go protocol because om_release() can do list deletions in parallel. We also lock-next-next-as-we-go to prevent an om_flush() that is behind this thread from passing us. Because this function can asynchronously interact with so many other functions, this is the largest clip of code:
L01: int ObjectSynchronizer::deflate_monitor_list_using_JT(ObjectMonitor** list_p,
L02: int* count_p,
L03: ObjectMonitor** free_head_p,
L04: ObjectMonitor** free_tail_p,
L05: ObjectMonitor** saved_mid_in_use_p) {
L06: JavaThread* self = JavaThread::current();
L07: ObjectMonitor* cur_mid_in_use = NULL;
L08: ObjectMonitor* mid = NULL;
L09: ObjectMonitor* next = NULL;
L10: ObjectMonitor* next_next = NULL;
L11: int deflated_count = 0;
L12: NoSafepointVerifier nsv;
L13: if (*saved_mid_in_use_p == NULL) {
L13L14: if ((mid = get_list_head_locked(list_p)) == NULL) {
L14L15: return 0; // The list is empty so nothing to deflate.
L15L16: }
L16L17: next = unmarked_next(mid);
L17L18: } else {
L18L19: cur_mid_in_use = *saved_mid_in_use_p;
L19L20: om_lock(cur_mid_in_use);
L20L21: mid = unmarked_next(cur_mid_in_use);
L21L22: if (mid == NULL) {
L22L23: om_unlock(cur_mid_in_use);
L23L24: *saved_mid_in_use_p = NULL;
L24L25: return 0; // The remainder is empty so nothing more to deflate.
L25L26: }
L26L27: om_lock(mid);
L27L28: next = unmarked_next(mid);
L28L29: }
L29L30: while (true) {
L30L31: if (next != NULL) {
L31L32: om_lock(next);
L32L33: next_next = unmarked_next(next);
L33L34: }
L34L35: if (mid->object() != NULL && mid->is_old() &&
L35L36: deflate_monitor_using_JT(mid, free_head_p, free_tail_p)) {
L36L37: if (cur_mid_in_use == NULL) {
L37L38: Atomic::store(list_p, next);
L38L39: } else {
L39L40: ObjectMonitor* locked_next = mark_om_ptr(next);
L40L41: cur_mid_in_use->set_next_om(locked_next);
L41L42: }
L42L43: deflated_count++;
L43L44: Atomic::dec(count_p);
L44L45: mid->set_next_om(NULL);
L45L46: mid = next; // mid keeps non-NULL next's locked state
L46L47: next = next_next;
L47L48: } else {
L48L49: if (cur_mid_in_use != NULL) {
L49L50: om_unlock(cur_mid_in_use);
L50L51: }
L51L52: cur_mid_in_use = mid;
L52L53: mid = next; // mid keeps non-NULL next's locked state
L53L54: next = next_next;
L54L55: if (SafepointMechanism::should_block(self) &&
L55L56: cur_mid_in_use != Atomic::load(list_p) && cur_mid_in_use->is_old()) {
L56L57: *saved_mid_in_use_p = cur_mid_in_use;
L57L58: om_unlock(cur_mid_in_use);
L58L59: if (mid != NULL) {
L59L60: om_unlock(mid);
L60L61: }
L61L62: return deflated_count;
L62L63: }
L63L64: }
L64L65: if (mid == NULL) {
L65L66: if (cur_mid_in_use != NULL) {
L66L67: om_unlock(cur_mid_in_use);
L67L68: }
L68L69: break; // Reached end of the list so nothing more to deflate.
L69L70: }
L70L71: }
L71L72: *saved_mid_in_use_p = NULL;
L72L73: return deflated_count;
L73L74: }
The above is not an exact copy of the code block from deflate_monitor_list_using_JT(), but it is the highlights. What the above code block needs to do is pretty simple:
...
Since we're using the more complicated lock-cur_mid_in_use-and-mid-as-we-go protocol and also the lock-next-next-as-we-go protocol, there is a mind numbing amount of detail:
...
((om_list_globals.population - om_list_globals.free_count) / om_list_globals.population) > NN%
(om_list_globals.population - om_list_globals.free_count) > MonitorBound
...
...
Other invocation changes by the Async Monitor Deflation project (when async deflation is enabled):
VM_Exit::doit_prologue() will request a special cleanup to reduce the noise in 'monitorinflation' logging at VM exit time.
Before the final safepoint in a non-System.exit() end to the VM, we will request a special cleanup to reduce the noise in 'monitorinflation' logging at VM exit time.
WB_G1StartMarkCycle()