The ObjectMonitor class only comes into play when a Java object lock is contended. If a stack lock is in use, then there is no ObjectMonitor and the successor protocol does not apply. The ObjectMonitor successor field (_succ) is declared in: hotspot/src/share/vm/runtime/objectMonitor.hpp 260 private: 261 Thread * volatile _succ ; // Heir presumptive thread - used for futile wakeup throttling - set to NULL in the ObjectMonitor() ctr - set to NULL by Recycle() function ============================================================== Successor Protocol for Contended Java Monitor Enter Operations ============================================================== Preconditions ============= - Java object lock is inflated - Java object lock is not owned - BiasedLocking is disabled - SyncFlags is default value of zero (0) - other Knob_* values are their default values Main ============================== volatile startCnt = 0 volatile slowCnt = 0 volatile quickCnt = 0 volatile doneCnt = 0 volatile doStress = true launch T1 launch T2 launch T3 while (true) { while (startCnt < 3) { sleep(500) } if (elapsed >= stressTime) { // if we are out of stress time, then tell all threads at once doStress = false } enter startBar startBar.notifyAll() exit startBar if (!doStress) { break } while (doneCnt < 3) { sleep(500) } startCnt = 0 slowCnt = 0 quickCnt = 0 doneCnt = 0 enter doneBar doneBar.notifyAll() exit doneBar } T1.join() T2.join() T3.join() Note: In the table below, the following shorthand values are used: '_own' => '_owner' field '_Resp' => '_Responsible' field '_Elst' => '_EntryList' field '_cxq' and '_succ' are short enough to be used as-is. We only care about 'monitor'; 'startBar', 'slowEnter', 'quickEnter' and 'doneBar' are for coordination of the test threads. The line numbers used in this table are based on the following version of src/share/vm/runtime/objectMonitor.cpp: changeset: 4801:f2110083203d user: sla date: Mon Jun 10 11:30:51 2013 +0200 summary: 8005849: JEP 167: Event-Based JVM Tracing This table requires a window at least 160 columns wide. =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== while (true) { | while (true) { | while (true) { | | | | | | enter startBar | enter startBar | enter startBar | | | | | | : | : | : | | | | | | startCnt++ | : | : | | | | | | startBar.wait() | : | : | | | | | | : | startCnt++ | : | | | | | | : | startBar.wait() | : | | | | | | : | : | startCnt++ | | | | | | : | : | startBar.wait() | | | | | | :
| : | : | | | | | | : | : | : | | | | | | exit startBar | : | : | | | | | | if (!doStress) { | exit startBar | : | | | | | | the main stress loop finished break | if (!doStress) { | exit startBar | | | | | | while we were waiting } | break | if (!doStress) { | | | | | | : | } | break | | | | | | : | : | } | | | | | | : | : | : | | | | | | =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== : | : | : | NULL | NULL | NULL | NULL | NULL | enter monitor | enter slowEnter | enter quickEnter | | | | | | : | : | : | T1 | | | | | lines 322-328 (in enter) while (slowCnt < 1) { | slowCnt++ | quickCnt++ | | | | | | sleep(500) | slowEnter.wait() | quickEnter.wait() | | | | | | } | : | : | | | | | | enter slowEnter | : | : | | | | | | slowEnter.notify() | : | : | | | | | | exit slowEnter | | : | | | | | | : | exit slowEnter | : | | | | | | while (quickCnt < 1) { | enter monitor | : | | | | | | sleep(500) | | : | | T2 | | | | line 356 (in enter) which calls } | : | : | | | | | | line 2047 (in TrySpin); this _succ=Self : | : | : | | | | | | is done by non-owner : | : | : | | NULL | | | | line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | | | Note: This non-NULL -> NULL transition : | : | : | | | | | | in TrySpin by a non-owner makes _succ : | : | : | | | | | | unstable which is why retries are needed : | : | : | | | | | | for proper progress : | : | : | | | | | | Note: after return from TrySpin, JVM/TI : | : | : | | | | | | MonitorContendedEnter posted by T2 here. : | : | : | | | | | | T2 can only be unparked by T1 ExitEpilog : | : | : | | | | | | after it is on _cxq which happens after : | : | : | | | | | | the event is posted. : | : | : | | | | T2 | | line 398 (in enter) which calls : | : | : | | | | | | line 535 to set _cxq (in EnterI) (after : | : | : | | | | | | calling TryLock and TrySpin again); : | : | : | | NULL | | | | line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | | | if _cxq CAS fails, does another TryLock : | : | : | | | T2 | | | line 573 (in EnterI) since 1st contender : | : | : | | | | | | TryLock after enqueing for progress : | : | : | | | | | | line 603 (in EnterI) T2 calls timed park : | : | : | | | | | | since _Resp==T2 : | : | : | | | | | | Note: exit path sets _Resp=NULL which : | : | : | | | | | | can cause non-timed park to be called : | : | : | | | | | | on line 609 instead : | : | : | | | | | | : | : | : | | | | | | enter quickEnter | : | : | | | | | | : | : | : | | | | | | quickEnter.notify() | : | : | | | | | | exit quickEnter | : | : | | | | | | exit monitor | : | exit quickEnter | | | | | | : | : | : | | | NULL | | | line 967 (in exit) : | : | : | | | | | | Note: the set _Resp=NULL above races with : | : | : | | | | | | T2's timed park call in EnterI : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T1 will return : | : | : | | | | | | early; T1 can observe T2's TrySpin changes : | : | : | | | | | | to _succ, but since T2 calls TryLock after : | : | : | | | | | | _succ=NULL, we make progress : | : | : | T1 | | | | | line 1040 (in exit) T1 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is empty : | : | : | | | | NULL | | lines 1184-1195 detach _cxq and : | : | : | | | | | T2 | lines 1228-1237 move to _Elst : | : | : | | | | | | line 1246: if a spinner has made itself the : | : | : | | | | | | successor, then we bail trying to pick a : | : | : | | | | | | successor from the queues : | : | : | | | | | | line 1251 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T2 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | line 1330 unpark(T2) and exit is done =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== : | : | : | NULL | T2 | NULL | NULL | T2 | : | : | enter monitor | | | | | | : | : | : | T3 | | | | | lines 322-328 (in enter) : | : | : | | | | | | Note: T3 took the fastpath enter here so a : | : | : | | | | | | JVM/TI MonitorContendedEntered event is not : | : | : | | | | | | posted; makes some sense since T3 did not : | : | : | | | | | | post a JVM/TI MonitorContendedEnter event. : | : | : | | | | | | Note: _succ==T2 still (for now) : | : | : | | | | | | T2: line 612 TryLock fails : | : | : | | | | | | T2: line 629 TrySpin fails : | : | : | | NULL | | | | T2: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | NULL | | | | T2: lines 643-646 _succ=NULL and fence : | : | : | | | | | | is followed by a TryLock for progress : | : | : | | | | | | _Resp==NULL so regular park is called : | : | : | | | | | | Note: _Resp remains unset because the logic : | : | : | | | | | | that sets it occurs before the loop that T2 : | : | : | | | | | | is executing. : | : | : | | | | | | : | : | : | | | | | | : | : | : | | | | | | : | : | exit monitor | | | | | | : | : | : | | | NULL | | | line 967 (in exit) : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T3 will return : | : | : | | | | | | early; T3 can observe the _succ=T2 value : | : | : | | | | | | set by T1 when it was exiting before T2 : | : | : | | | | | | can set _succ=NULL on lines 643-646, but : | : | : | | | | | | since T2 calls TryLock after _succ=NULL, : | : | : | | | | | | we make progress : | : | : | T3 | | | | | line 1040 (in exit) T3 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is not empty : | : | : | | | | | | line 1178 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T2 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | line 1330 unpark(T2) and exit is done =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== : | : | : | NULL | T2 | NULL | NULL | T2 | : | : | : | T2 | | | | | calls TryLock (line 612) or TrySpin (line : | : | : | | | | | | 629) in EnterI, one of which works : | : | : | | | | | NULL | line 663 UnlinkAfterAcquire to remove T2's : | : | : | | | | | | Waiter node from queues : | : | : | | NULL | | | | line 664 _succ=NULL : | : | : | | | | | | line 667-668 if (_Resp==Self) _Resp=NULL : | : | : | | | | | | Note: JVM/TI MonitorContendedEntered posted : | : | : | | | | | | by T2 on line 441 (in enter). T2 owns the : | : | : | | | | | | monitor so there is no unpark to be lost. : | : | : | | | | | | : | : | : | | | | | | : | exit monitor | : | | | | | | : | : | : | | | NULL | | | line 967 (in exit) : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | lines 997-999 _Elst and _cxq are empty so : | : | : | | | | | | return early since there is no successor : | : | : | | | | | | enter doneBar | enter doneBar | enter doneBar | | | | | | : | : | : | | | | | | doneCnt++ | : | : | | | | | | doneBar.wait() | : | : | | | | | | : | doneCnt++ | : | | | | | | : | doneBar.wait() | : | | | | | | : | : | doneCnt++ | | | | | | : | : | doneBar.wait() | | | | | | :
| : | : | | | | | | : | : | : | | | | | | exit doneBar | : | : | | | | | | : | exit doneBar | : | | | | | | : | : | exit doneBar | | | | | | } | } | } | | | | | | =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | =========================================== ============================================================= Successor Protocol for Contended Java Monitor Wait Operations ============================================================= Preconditions ============= - Java object lock is inflated - Java object lock is not owned - BiasedLocking is disabled - SyncFlags is default value of zero (0) - other Knob_* values are their default values Main ============================== ballOwner = DEF_N_THREADS launch N worker threads (DEF_N_THREADS == 3) while (true) { if (elapsed >= stressTime) { // if we are out of stress time, then tell all threads at once doStress = false break } sleep(1 sec) } enter monitor // wake up any thread that called wait() before seeing !doStress monitor.notifyAll() exit monitor Thread.join() N worker threads // end Main playRoundTheBases(myThread) { while (doStress) { if (threads[ballOwner] == myThread) { // myThread has the "ball" so "throw" it to the next "base" if (++ballOwner >= threads.length) { ballOwner = 1 // wrap around to the beginning "base" } // wake up all the threads monitor.notifyAll() } monitor.wait() // wait until the ball is "thown" } } Worker Thread ============================== enter monitor playRoundTheBases(this) exit monitor // end Worker Thread Note: In the table below, the following shorthand values are used: '_own' => '_owner' field '_Resp' => '_Responsible' field '_Elst' => '_EntryList' field '_cxq' and '_succ' are short enough to be used as-is. We only care about 'monitor'. The line numbers used in this table are based on the following version of src/share/vm/runtime/objectMonitor.cpp: changeset: 4801:f2110083203d user: sla date: Mon Jun 10 11:30:51 2013 +0200 summary: 8005849: JEP 167: Event-Based JVM Tracing This table requires a window at least 175 columns wide. =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== : | : | : | NULL | NULL | NULL | NULL | NULL | enter monitor | enter monitor | enter monitor | | | | | | : | : | : | T1 | | | | | T1: lines 322-328 (in enter) : | : | : | | T2 | | | | T2: line 356 (in enter) which calls : | : | : | | | | | | line 2047 (in TrySpin); this _succ=Self : | : | : | | | | | | is done by non-owner : | : | : | | NULL | | | | T2: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | | | Note: This non-NULL -> NULL transition : | : | : | | | | | | in TrySpin by a non-owner makes _succ : | : | : | | | | | | unstable which is why retries are needed : | : | : | | | | | | for proper progress : | : | : | | | | | | T3: if T2 and T3 are in TrySpin at the : | : | : | | | | | | same time, then _succ only shows T2. T3 : | : | : | | | | | | could TrySpin after T2 has aborted which : | : | : | | | | | | means _succ would be set to T3 and NULL. : | : | : | | | | | | Note: after return from TrySpin, JVM/TI : | : | : | | | | | | MonitorContendedEnter posted by T2 and : | : | : | | | | | | T3 here. T2 or T3 can only be unparked by : | : | : | | | | | | T1 ExitEpilog after either is on _cxq : | : | : | | | | | | which happens after the event is posted. : | : | : | | | | | | T2: line 398 (in enter) calls EnterI : | : | : | | | | | | T2: in EnterI calls TryLock and TrySpin : | : | : | | | | | | again before trying to set _cxq : | : | : | | NULL | | | | T2: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | T2 | | T2: line 535 to set _cxq (in EnterI); : | : | : | | | | | | if _cxq CAS fails, does another TryLock : | : | : | | | T2 | | | T2: line 573 (in EnterI) since 1st contender : | : | : | | | | | | TryLock after enqueing for progress : | : | : | | | | | | line 603 (in EnterI) T2 calls timed park : | : | : | | | | | | since _Resp==T2 : | : | : | | | | | | Note: exit path sets _Resp=NULL which : | : | : | | | | | | can cause non-timed park to be called : | : | : | | | | | | on line 609 instead : | : | : | | | | | | T3: line 398 (in enter) calls EnterI : | : | : | | | | | | T3: in EnterI calls TryLock and TrySpin : | : | : | | | | | | again before trying to set _cxq : | : | : | | NULL | | | | T3: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | T3, | | T3: line 535 to insert at front of _cxq : | : | : | | | | T2 | | (in EnterI); if _cxq CAS fails, does : | : | : | | | | | | another TryLock : | : | : | | | T2 | | | T3: line 573 (in EnterI) but _Resp is : | : | : | | | | | | already set : | : | : | | | | | | T3 calls park (line 609) since _Resp==T2 =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== : | : | : | T1 | NULL | T2 | T3, | NULL | : | : | : | | | | T2 | | playRoundTheBases(this) | : | : | | | | | | while (doStress) { | : | : | | | | | | ! if ball is mine { | : | : | | | | | | ! } | : | : | | | | | | monitor.wait() | : | : | | | | | | Note: The JVM_MonitorWait entry point posts : | : | : | | | | | | a JVM/TI MonitorWait event before : | : | : | | | | | | ObjectSynchronizer::wait is called and T1 : | : | : | | | | | | still owns the monitor so there is no unpark : | : | : | | | | | | to be lost. : | : | : | | | | | | Note: If T1's wait() call is interruptible, : | : | : | | | | | | T1 has been interrupted and T1 does not have : | : | : | | | | | | a pending exception, then wait() bails early : | : | : | | | | | | with an InterruptedException. This path also : | : | : | | | | | | posts a JVM/TI MonitorWaited event. : | : | : | | | NULL | | | line 1494 (in wait) before calling exit : | : | : | | | | | | call exit() on line 1499 (in wait) : | : | : | | | NULL | | | line 967 (in exit) : | : | : | | | | | | Note: either of the set _Resp=NULL above : | : | : | | | | | | races with T2's or T3's park calls in EnterI : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T1 will return : | : | : | | | | | | early; T1 can observe T2's or T3's TrySpin : | : | : | | | | | | changes to _succ, but since T2 or T3 call : | : | : | | | | | | TryLock after _succ=NULL, we make progress : | : | : | T1 | | | | | line 1040 (in exit) T1 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is empty : | : | : | | | | NULL | | lines 1184-1195 detach _cxq and : | : | : | | | | | T3, | lines 1228-1237 move to _Elst : | : | : | | | | | T2 | line 1246: if a spinner has made itself the : | : | : | | | | | | successor, then we bail trying to pick a : | : | : | | | | | | successor from the queues : | : | : | | | | | | line 1251 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T3 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : timed out | : | | | | | | followed by fence; T2 park() timed out : | : | : | | | | | | line 1330 unpark(T3) and exit is done =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== : | : | : | NULL | T3 | NULL | NULL | T3, | : | : | : | | | | | T2 | T1: Now back in wait on line 1500 : | : | : | | | | | | T1: calls park on line 1539 (in wait) : | : | : | T2 | | | | | T2: calls TryLock (line 612) or TrySpin (line : | : | : | | | | | | 629) in EnterI, one of which works : | : | : | | | | | | Note: _succ==T3 still (for now) : | : | : | | | | | T3 | T2: line 663 UnlinkAfterAcquire to remove T2's : | : | : | | | | | | Waiter node from queues : | : | : | | NULL | | | | T2: line 664 _succ=NULL : | : | : | | | | | | T2: line 667-668 if (_Resp==Self) _Resp=NULL : | : | : | | | | | | Note: JVM/TI MonitorContendedEntered posted : | : | : | | | | | | by T2 on line 441 (in enter). T2 owns the : | : | : | | | | | | monitor so there is no unpark to be lost. : | : | : | | | | | | T3: line 612 TryLock fails : | : | : | | | | | | T3: line 629 TrySpin fails : | : | : | | NULL | | | | T3: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | NULL | | | | T3: lines 643-646 _succ=NULL and fence : | : | : | | | | | | is followed by a TryLock for progress : | : | : | | | | | | _Resp==NULL so regular park is called : | : | : | | | | | | Note: _Resp remains unset because the logic : | : | : | | | | | | that sets it occurs before the loop that T3 : | : | : | | | | | | is executing. : | playRoundTheBases(this) | : | | | | | | : | while (doStress) { | : | | | | | | : | ! if ball is mine { | : | | | | | | : | ! } | : | | | | | | : | monitor.wait() | : | | | | | | Note: The JVM_MonitorWait entry point posts : | : | : | | | | | | a JVM/TI MonitorWait event before : | : | : | | | | | | ObjectSynchronizer::wait is called and T2 : | : | : | | | | | | still owns the monitor so there is no unpark : | : | : | | | | | | to be lost. : | : | : | | | | | | Note: If T2's wait() call is interruptible, : | : | : | | | | | | T2 has been interrupted and T2 does not have : | : | : | | | | | | a pending exception, then wait() bails early : | : | : | | | | | | with an InterruptedException. This path also : | : | : | | | | | | posts a JVM/TI MonitorWaited event. : | : | : | | | NULL | | | line 1494 (in wait) before calling exit : | : | : | | | | | | call exit() on line 1499 (in wait) : | : | : | | | NULL | | | line 967 (in exit) : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T2 will return : | : | : | | | | | | early : | : | : | T2 | | | | | line 1040 (in exit) T2 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is not empty : | : | : | | | | | | line 1178 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T3 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | line 1330 unpark(T3) and exit is done =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== : | : | : | NULL | T3 | NULL | NULL | T3 | : | : | : | | | | | | T2: Now back in wait on line 1500 : | : | : | | | | | | T2: calls park on line 1539 (in wait) : | : | : | T3 | | | | | T3: calls TryLock (line 612) or TrySpin (line : | : | : | | | | | | 629) in EnterI, one of which works : | : | : | | | | | | Note: _succ==T3 still (for now) : | : | : | | | | | NULL | T3: line 663 UnlinkAfterAcquire to remove T3's : | : | : | | | | | | Waiter node from queues : | : | : | | | | | | Note: JVM/TI MonitorContendedEntered posted : | : | : | | | | | | by T3 on line 441 (in enter). T3 owns the : | : | : | | | | | | monitor so there is no unpark to be lost. : | : | : | | NULL | | | | T3: _succ=NULL (line 664) : | : | : | | | | | | T3: line 667-668 if (_Resp==Self) _Resp=NULL : | : | playRoundTheBases(this) | | | | | | : | : | while (doStress) { | | | | | | : | : | if ball is mine { | | | | | | : | : | ballOwner = 1 | | | | | | : | : | monitor.notifyAll() | | | | T1, | | Note: By default, notifyAll() simply moves the : | : | } | | | | T2 | | ObjectWaiter for each waiting thread from the : | : | : | | | | | | WaitSet to the front of the cxq so the waiting : | : | : | | | | | | threads are all still parked. : | : | : | | | | | | Note: Knob_MoveNotifyee values 0..3 specify : | : | : | | | | | | moving the ObjectWaiter to different positions : | : | : | | | | | | in different queues; Knob_MoveNotifyee > 3 : | : | : | | | | | | results in an immediate unpark on line 1758 : | : | : | | | | | | of a single thread for notify() and on line : | : | : | | | | | | 1882 for all waiting threads for notifyAll(). : | : | monitor.wait() | | | | | | Note: The JVM_MonitorWait entry point posts : | : | : | | | | | | a JVM/TI MonitorWait event before : | : | : | | | | | | ObjectSynchronizer::wait is called and T3 : | : | : | | | | | | still owns the monitor so there is no unpark : | : | : | | | | | | to be lost. : | : | : | | | | | | Note: If T3's wait() call is interruptible, : | : | : | | | | | | T3 has been interrupted and T3 does not have : | : | : | | | | | | a pending exception, then wait() bails early : | : | : | | | | | | with an InterruptedException. This path also : | : | : | | | | | | posts a JVM/TI MonitorWaited event. : | : | : | | | NULL | | | line 1494 (in wait) before calling exit : | : | : | | | | | | call exit() on line 1499 (in wait) : | : | : | | | NULL | | | line 967 (in exit) : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T3 will return : | : | : | | | | | | early; XXX not sure that can happen here : | : | : | T3 | | | | | line 1040 (in exit) T3 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is empty : | : | : | | | | NULL | | lines 1184-1195 detach _cxq and : | : | : | | | | | T1, | lines 1228-1237 move to _Elst : | : | : | | | | | T2 | line 1246: if a spinner has made itself the : | : | : | | | | | | successor, then we bail trying to pick a : | : | : | | | | | | successor from the queues : | : | : | | | | | | line 1251 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T1 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | line 1330 unpark(T1) and exit is done =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | ============================================== : | : | : | NULL | T1 | NULL | NULL | T1, | : | : | : | | | | | T2 | T3: Now back in wait on line 1500 : | : | : | | | | | | Note: There is a comment on lines 1502-1512 : | : | : | | | | | | about the exiting thread posting a : | : | : | | | | | | MONITOR_CONTENDED_EXIT event. There is no : | : | : | | | | | | such event. The thread will post a JVM/TI : | : | : | | | | | | MonitorWaited event below after the park : | : | : | | | | | | call below so there's no way the event : | : | : | | | | | | handler for the JVM/TI MonitorWaited event : | : | : | | | | | | can accidentally consume the unpark meant : | : | : | | | | | | for the park on line 1539. So I don't see : | : | : | | | | | | how the unpark on line 1514 does any good. : | : | : | | | | | | T3 calls park on line 1539 (in wait) : | : | : | | NULL | | | | T1: line 1585 sets _succ=NULL : | : | : | | | | | | T1: posts a JVM/TI MonitorWaited event : | : | : | | | | | | T1: calls ReenterI on line 1615 in wait : | : | : | T1 | | | | | T1: calls TryLock (line 743) or TrySpin : | : | : | | | | | | (line 744) in ReenterI, one of which works : | : | : | | | | | T2 | line 809 UnlinkAfterAcquire to remove T1's : | : | : | | | | | | Waiter node from queues | : | : | | | | | | if ball is mine { | : | : | | | | | | ballOwner = 2 | : | : | | | | | | monitor.notifyAll() | : | : | | | | | | } | : | : | | | | | | monitor.wait() | : | : | | | | | | : | | : | | | | | | : | if ball is mine { | : | | | | | | : | ballOwner = 3 | : | | | | | | : | monitor.notifyAll() | : | | | | | | : | } | : | | | | | | : | monitor.wait() | : | | | | | | : | : | | | | | | | : | : | if ball is mine { | | | | | | : | : | ballOwner = 1 | | | | | | : | : | monitor.notifyAll() | | | | | | : | : | } | | | | | | : | : | monitor.wait() | | | | | | | : | : | | | | | | if ball is mine { | : | : | | | | | | ballOwner = 2 | : | : | | | | | | monitor.notifyAll() | : | : | | | | | | } | : | : | | | | | | monitor.wait() | : | : | | | | | | : | | : | | | | | | : | if ball is mine { | : | | | | | | : | ballOwner = 3 | : | | | | | | : | monitor.notifyAll() | : | | | | | | : | } | : | | | | | | : | monitor.wait() | : | | | | | | : | : | | | | | | | : | : | if ball is mine { | | | | | | : | : | ballOwner = 1 | | | | | | : | : | monitor.notifyAll() | | | | | | : | : | } | | | | | | : | : | monitor.wait() | | | | | | | | | | | | | | } | } | } | | | | | | exit monitor | exit monitor | exit monitor | | | | | | | | | | | | | | =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | =============================== T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | =========================== | =========================== | ==== | ===== | ===== | ==== | ===== | =============================== ============================================================== Successor Protocol for Contended Java Monitor Combo Operations ============================================================== Preconditions ============= - Java object lock is inflated - Java object lock is not owned - BiasedLocking is disabled - SyncFlags is default value of zero (0) - other Knob_* values are their default values Main ============================== volatile startCnt = 0 volatile slowCnt = 0 volatile quickCnt = 0 volatile doneCnt = 0 volatile doStress = true launch T1 launch T2 launch T3 while (true) { while (startCnt < 3) { sleep(500) } if (elapsed >= stressTime) { // if we are out of stress time, then tell all threads at once doStress = false } enter startBar startBar.notifyAll() exit startBar if (!doStress) { break } while (doneCnt < 3) { sleep(500) } startCnt = 0 slowCnt = 0 quickCnt = 0 doneCnt = 0 enter doneBar doneBar.notifyAll() exit doneBar } T1.join() T2.join() T3.join() Note: In the table below, the following shorthand values are used: '_own' => '_owner' field '_Resp' => '_Responsible' field '_Elst' => '_EntryList' field '_cxq' and '_succ' are short enough to be used as-is. We only care about 'monitor'; 'startBar', 'slowEnter', 'quickEnter' and 'doneBar' are for coordination of the test threads. The line numbers used in this table are based on the following version of src/share/vm/runtime/objectMonitor.cpp: changeset: 4801:f2110083203d user: sla date: Mon Jun 10 11:30:51 2013 +0200 summary: 8005849: JEP 167: Event-Based JVM Tracing This table requires a window at least 160 columns wide. =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ enter monitor | | | | | | | | : | | | T1 | | | | | lines 322-328 (in enter) while (true) { | while (true) { | while (true) { | | | | | | enter startBar | enter startBar | enter startBar | | | | | | : | : | : | | | | | | startCnt++ | : | : | | | | | | startBar.wait() | : | : | | | | | | : | startCnt++ | : | | | | | | : | startBar.wait() | : | | | | | | : | : | startCnt++ | | | | | | : | : | startBar.wait() | | | | | | :
| : | : | | | | | | : | : | : | | | | | | exit startBar | : | : | | | | | | if (!doStress) { | exit startBar | : | | | | | | the main stress loop finished break | if (!doStress) { | exit startBar | | | | | | while we were waiting } | break | if (!doStress) { | | | | | | : | } | break | | | | | | : | : | } | | | | | | : | : | : | | | | | | =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ : | : | : | T1 | NULL | NULL | NULL | NULL | : | : | : | | | | | | T2: debug_sync_pause(1) : | : | : | | | | | | T3: debug_sync_pause(2) : | : | : | | | | | | T4: debug_sync_pause(4) : | enter monitor | : | | | | | | : | : | : | | T2 | | | | T2: line 356 (in enter) which calls : | : | : | | | | | | line 2047 (in TrySpin); this _succ=Self : | : | : | | | | | | is done by non-owner : | : | : | | NULL | | | | T2: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | | | Note: This non-NULL -> NULL transition : | : | : | | | | | | in TrySpin by a non-owner makes _succ : | : | : | | | | | | unstable which is why retries are needed : | : | : | | | | | | for proper progress : | : | : | | | | | | Note: after return from TrySpin, JVM/TI : | : | : | | | | | | MonitorContendedEnter posted by T2 here. : | : | : | | | | | | T2 is not on any queue yet so an ExitEpilog : | : | : | | | | | | unpark cannot see T2 yet. : | : | : | | | | | | T2: line 398 (in enter) calls EnterI : | : | : | | | | | | T2: in EnterI calls TryLock and TrySpin : | : | : | | | | | | again before trying to set _cxq : | : | : | | NULL | | | | T2: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | | | T2 | | T2: line 535 to set _cxq (in EnterI); if : | : | : | | | | | | if _cxq CAS fails, does another TryLock : | : | : | | | T2 | | | T2: line 573 (in EnterI) since 1st contender : | : | : | | | | | | TryLock after enqueing for progress : | : | : | | | | | | line 603 (in EnterI) T2 calls timed park : | : | : | | | | | | since _Resp==T2 : | : | : | | | | | | Note: exit path sets _Resp=NULL which : | : | : | | | | | | can cause non-timed park to be called : | : | : | | | | | | on line 609 instead =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ : | : | : | T1 | NULL | T2 | T2 | NULL | monitor.wait() | : | : | | | | | | Note: The JVM_MonitorWait entry point : | : | : | | | | | | posts a JVM/TI MonitorWait event before : | : | : | | | | | | ObjectSynchronizer::wait is called. : | : | : | | | | | | Note: If T1's wait() call is interruptible, : | : | : | | | | | | T1 has been interrupted and T1 does not have : | : | : | | | | | | a pending exception, then wait() bails early : | : | : | | | | | | with an InterruptedException. This path also : | : | : | | | | | | posts a JVM/TI MonitorWaited event. : | : | : | | | NULL | | | line 1494 (in wait) before calling exit : | : | : | | | | | | call exit() on line 1499 (in wait) : | : | : | | | NULL | | | line 967 (in exit) : | : | : | | | | | | Note: either of the set _Resp=NULL above : | : | : | | | | | | races with T2's park call in EnterI : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T1 will return : | : | : | | | | | | early; T1 can observe T2's TrySpin changes : | : | : | | | | | | to _succ, but since T2 calls TryLock after : | : | : | | | | | | _succ=NULL, we make progress : | : | : | T1 | | | | | line 1040 (in exit) T1 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is empty : | : | : | | | | NULL | | lines 1184-1195 detach _cxq and : | : | : | | | | | T2 | lines 1228-1237 move to _Elst : | : | : | | | | | | line 1246: if a spinner has made itself the : | : | : | | | | | | successor, then we bail trying to pick a : | : | : | | | | | | successor from the queues : | : | : | | | | | | line 1251 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T2 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | line 1330 unpark(T2) and exit is done =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ : | : | : | NULL | T2 | NULL | NULL | T2 | : | : | : | | | | | | T1: Now back in wait on line 1500 : | : | : | | | | | | Note: There is a comment on lines 1502-1512 : | : | : | | | | | | about the exiting thread posting a : | : | : | | | | | | MONITOR_CONTENDED_EXIT event. There is no : | : | : | | | | | | such event. The thread will post a JVM/TI : | : | : | | | | | | MonitorWaited event below after the park : | : | : | | | | | | call below so there's no way the event : | : | : | | | | | | handler for the JVM/TI MonitorWaited event : | : | : | | | | | | can accidentally consume the unpark meant : | : | : | | | | | | for the park on line 1539. So I don't see : | : | : | | | | | | how the unpark on line 1514 does any good. : | : | : | | | | | | T1: debug_sync_inc(); counter==1; resume T2 : | : | : | | | | | | T1: calls park on line 1539 (in wait) : | : | : | T2 | | | | | T2: calls TryLock (line 612) or TrySpin : | : | : | | | | | | (line 629) in EnterI, one of which works : | : | : | | | | | NULL | T2: line 663 UnlinkAfterAcquire to remove : | : | : | | | | | | T2's Waiter node from queues : | : | : | | NULL | | | | T2: line 664 _succ=NULL : | : | : | | | | | | T2: line 667-668 if (_Resp==Self) _Resp=NULL : | : | : | | | | | | Note: JVM/TI MonitorContendedEntered posted : | : | : | | | | | | by T2 on line 441 (in enter). : | monitor.notify() | : | | | | T1 | | Note: By default, notify() simply moves the : | : | : | | | | | | ObjectWaiter for a waiting thread from the : | : | : | | | | | | WaitSet to the front of the cxq so the : | : | : | | | | | | waiting thread is still parked. : | : | : | | | | | | Note: Knob_MoveNotifyee values 0..3 specify : | : | : | | | | | | moving the ObjectWaiter to different positions : | : | : | | | | | | in different queues; Knob_MoveNotifyee > 3 : | : | : | | | | | | results in an immediate unpark on line 1758 : | : | : | | | | | | of a single thread for notify() and on line : | : | : | | | | | | 1882 for all waiting threads for notifyAll(). : | exit monitor | : | | | NULL | | | line 967 (in exit) : | : | : | NULL | | | | | line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T2 will return : | : | : | | | | | | early; T2 can observe T3's TrySpin changes : | : | : | | | | | | to _succ, but since T3 calls TryLock after : | : | : | | | | | | _succ=NULL, we make progress : | : | : | T2 | | | | | line 1040 (in exit) T2 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | If that fails that means another thread has : | : | : | | | | | | grabbed the lock and we still progress. : | : | : | | | | | | line 1165 _Elst is empty : | : | : | | | | NULL | | lines 1184-1195 detach _cxq and : | : | : | | | | | T1 | lines 1228-1237 move to _Elst : | : | : | | | | | | line 1246: if a spinner has made itself the : | : | : | | | | | | successor, then we bail trying to pick a : | : | : | | | | | | successor from the queues : | : | : | | | | | | line 1251 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T1 | | | | line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | T2: debug_sync_inc(); counter==2; resume T3 : | : | : | | | | | | line 1330 unpark(T1) and exit is done =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ : | : | : | NULL | T1 | NULL | NULL | T1 | : | : | : | | | | | | T1: Now back in wait after park on line 1539 : | : | : | | NULL | | | | T1: line 1585 sets _succ=NULL : | : | : | | | | | | T1: debug_sync_pause(3) : | : | enter monitor | | | | | | : | : | : | T3 | | | | | T3: lines 322-328 (in enter) : | : | : | | | | | | Note: T3 took the fastpath enter here so a : | : | : | | | | | | JVM/TI MonitorContendedEntered event is not : | : | : | | | | | | posted; makes some sense since T3 did not : | : | : | | | | | | post a JVM/TI MonitorContendedEnter event. : | : | exit monitor | | | | | | : | : | : | | | NULL | | | T3: line 967 (in exit) : | : | : | NULL | | | | | T3: line 995 (in exit) followed by storeload : | : | : | | | | | | Note: If _succ!=NULL, then T3 will return early : | : | : | T3 | | | | | T3: line 1040 (in exit) T3 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | T3: line 1165 _Elst is not empty : | : | : | | | | | | T3: line 1178 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T1 | | | | T3: line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | T3: line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | T3: line 1330 unpark(T1) and exit is done : | : | : | | | | | | T3: debug_sync_inc(); counter==3; resume T1 : | : | : | | | | | | Note: T1 isn't parked, but the unpark is saved : | : | enter monitor | | | | | | : | : | : | T4 | | | | | T4: lines 322-328 (in enter) : | : | : | | | | | | T4: debug_sync_inc(); counter==5; resume T1 : | : | : | | | | | | T1: posts a JVM/TI MonitorWaited event : | : | : | | | | | | T1: event handler consumes the unpark : | : | : | | | | | | T1: calls ReenterI on line 1615 in wait : | : | : | | | | | | T1: debug_sync_inc(); counter==4; resume T4 : | : | : | | | | | | T1: debug_sync_pause(5) : | : | : | | | | | | T1: calls TryLock (line 743) or TrySpin : | : | : | | | | | | (line 744) in ReenterI, both of which fail : | : | : | | | | | | because T3 owns the lock : | : | : | | NULL | | | | T1: line 2191 (abort in TrySpin); this : | : | : | | | | | | _succ=NULL is following by fence and : | : | : | | | | | | a TryLock retry for progress : | : | : | | T1 | | | | T1-opt: line 2023-2025 in TrySpin checks : | : | : | | | | | | for a NotRunnable(_own) and returns without : | : | : | | | | | | setting _succ=NULL : | : | : | | | | | | T1: debug_sync_inc(); counter==6; resume T4 : | : | : | | | | | | T1: calls park on line 761 in ReenterI : | : | exit monitor | | | | | | : | : | : | | | NULL | | | T4: line 967 (in exit) : | : | : | | | | | | T4: debug_sync_pause(6) : | : | : | NULL | | | | | T4: line 995 (in exit) followed by storeload : | : | : | | | | | | T4-opt: If _succ!=NULL, then T4 will return : | : | : | | | | | | early which will strand T1 : | : | : | T4 | | | | | T4: line 1040 (in exit) T4 attempts to regain : | : | : | | | | | | ownership so that queues can be managed. : | : | : | | | | | | T4: line 1165 _Elst is not empty : | : | : | | | | | | T4: line 1178 exit calls ExitEpilog with _Elst : | : | : | | | | | | value : | : | : | | T1 | | | | T4: line 1313 (in ExitEpilog) sets _succ : | : | : | NULL | | | | | T4: line 1322 (in ExitEpilog) sets _own=NULL : | : | : | | | | | | followed by fence : | : | : | | | | | | T4: line 1330 unpark(T1) and exit is done : | : | : | T1 | | | | | T1: line 776 TryLock (in ReenterI) works : | : | : | | | | | | No hang : | : | : | | | | | | =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ while (doneCnt < 2) { | enter doneBar | enter doneBar | | | | | | monitor.wait(100) | : | : | | | | | | T1 allows others to get past monitor : | doneCnt++ | : | | | | | | : | doneBar.wait() | : | | | | | | : | : | doneCnt++ | | | | | | } | : | doneBar.wait() | | | | | | enter doneBar | : | : | | | | | | : | : | : | | | | | | doneCnt++ | : | : | | | | | | doneBar.wait() | : | : | | | | | | : | : | : | | | | | | : | : | : | | | | | | : | : | : | | | | | | exit doneBar | : | : | | | | | | : | exit doneBar | : | | | | | | : | : | exit doneBar | | | | | | } | } | } | | | | | | exit monitor | | | | | | | | =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================ T1 | T2 | T3 | _own | _succ | _Resp | _cxq | _Elst | Notes =========================== | ===================== | ===================== | ==== | ===== | ===== | ==== | ===== | ============================================