{"id":117804,"date":"2026-10-25T13:16:25","date_gmt":"2026-10-25T05:16:25","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/117804.html"},"modified":"2026-10-25T13:16:25","modified_gmt":"2026-10-25T05:16:25","slug":"how-monitors-are-implemented-from-the-os-kernel-to-the-hotspot-objectmonitor","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/117804.html","title":{"rendered":"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor"},"content":{"rendered":"<p>\u4e2d\u6587\u7248&#xff1a;\u7ba1\u7a0b\u662f\u600e\u4e48\u5b9e\u73b0\u7684&#xff1a;\u4ece\u64cd\u4f5c\u7cfb\u7edf\u5185\u6838\u5230 HotSpot \u5bf9\u8c61\u76d1\u89c6\u5668<\/p>\n<p>The companion article, From PV Operations and Monitors to Modern Java Concurrency, covers what a monitor is: private data, procedures, implicit mutual exclusion, condition variables, and the Hoare-versus-Mesa signalling question. This one asks the next question \u2014 how is it built? \u2014 and follows the answer from a single atomic CPU instruction, through Linux wait queues and futexes, up to the object header and ObjectMonitor that sit behind every synchronized block in HotSpot.<\/p>\n<h3>Abbreviations<\/h3>\n<table>\n<tr>AbbreviationFull name<\/tr>\n<tbody>\n<tr>\n<td>OS<\/td>\n<td>Operating System<\/td>\n<\/tr>\n<tr>\n<td>CPU<\/td>\n<td>Central Processing Unit<\/td>\n<\/tr>\n<tr>\n<td>CAS<\/td>\n<td>Compare-And-Swap<\/td>\n<\/tr>\n<tr>\n<td>TAS<\/td>\n<td>Test-And-Set<\/td>\n<\/tr>\n<tr>\n<td>LL\/SC<\/td>\n<td>Load-Linked \/ Store-Conditional<\/td>\n<\/tr>\n<tr>\n<td>MCS<\/td>\n<td>Mellor-Crummey and Scott (queue lock)<\/td>\n<\/tr>\n<tr>\n<td>futex<\/td>\n<td>Fast Userspace muTEX<\/td>\n<\/tr>\n<tr>\n<td>NPTL<\/td>\n<td>Native POSIX Thread Library (glibc\u2019s threading implementation)<\/td>\n<\/tr>\n<tr>\n<td>POSIX<\/td>\n<td>Portable Operating System Interface<\/td>\n<\/tr>\n<tr>\n<td>JVM<\/td>\n<td>Java Virtual Machine<\/td>\n<\/tr>\n<tr>\n<td>JDK<\/td>\n<td>Java Development Kit<\/td>\n<\/tr>\n<tr>\n<td>JEP<\/td>\n<td>JDK Enhancement Proposal<\/td>\n<\/tr>\n<tr>\n<td>JUC<\/td>\n<td>java.util.concurrent<\/td>\n<\/tr>\n<tr>\n<td>AQS<\/td>\n<td>AbstractQueuedSynchronizer<\/td>\n<\/tr>\n<tr>\n<td>GC<\/td>\n<td>Garbage Collection<\/td>\n<\/tr>\n<tr>\n<td>IPI<\/td>\n<td>Inter-Processor Interrupt<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>0. Three problems every implementation must solve<\/h3>\n<p>Whatever layer you look at, a monitor implementation has to answer the same three mechanical questions:<\/p>\n<li>Mutual exclusion \u2014 how do we guarantee at most one thread is inside?<\/li>\n<li>Condition waiting \u2014 wait(c) must release the lock and go to sleep; signal(c) must wake one sleeper. If another thread slips in between those two halves, the signal is lost (the lost wake-up problem). How is that prevented?<\/li>\n<li>Blocking and waking \u2014 when a thread cannot proceed, how does it actually stop consuming CPU, and how is it brought back?<\/li>\n<p>Every design below is one particular answer to these three.<\/p>\n<p>  #mermaid-svg-z3HZspTYReemt27E{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z3HZspTYReemt27E .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z3HZspTYReemt27E .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z3HZspTYReemt27E .error-icon{fill:#552222;}#mermaid-svg-z3HZspTYReemt27E .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z3HZspTYReemt27E .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z3HZspTYReemt27E .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z3HZspTYReemt27E .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z3HZspTYReemt27E .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z3HZspTYReemt27E .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z3HZspTYReemt27E .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z3HZspTYReemt27E .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z3HZspTYReemt27E .marker.cross{stroke:#333333;}#mermaid-svg-z3HZspTYReemt27E svg{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z3HZspTYReemt27E p{margin:0;}#mermaid-svg-z3HZspTYReemt27E .label{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;color:#333;}#mermaid-svg-z3HZspTYReemt27E .cluster-label text{fill:#333;}#mermaid-svg-z3HZspTYReemt27E .cluster-label span{color:#333;}#mermaid-svg-z3HZspTYReemt27E .cluster-label span p{background-color:transparent;}#mermaid-svg-z3HZspTYReemt27E .label text,#mermaid-svg-z3HZspTYReemt27E span{fill:#333;color:#333;}#mermaid-svg-z3HZspTYReemt27E .node rect,#mermaid-svg-z3HZspTYReemt27E .node circle,#mermaid-svg-z3HZspTYReemt27E .node ellipse,#mermaid-svg-z3HZspTYReemt27E .node polygon,#mermaid-svg-z3HZspTYReemt27E .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z3HZspTYReemt27E .rough-node .label text,#mermaid-svg-z3HZspTYReemt27E .node .label text,#mermaid-svg-z3HZspTYReemt27E .image-shape .label,#mermaid-svg-z3HZspTYReemt27E .icon-shape .label{text-anchor:middle;}#mermaid-svg-z3HZspTYReemt27E .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z3HZspTYReemt27E .rough-node .label,#mermaid-svg-z3HZspTYReemt27E .node .label,#mermaid-svg-z3HZspTYReemt27E .image-shape .label,#mermaid-svg-z3HZspTYReemt27E .icon-shape .label{text-align:center;}#mermaid-svg-z3HZspTYReemt27E .node.clickable{cursor:pointer;}#mermaid-svg-z3HZspTYReemt27E .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z3HZspTYReemt27E .arrowheadPath{fill:#333333;}#mermaid-svg-z3HZspTYReemt27E .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z3HZspTYReemt27E .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z3HZspTYReemt27E .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z3HZspTYReemt27E .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z3HZspTYReemt27E .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z3HZspTYReemt27E .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z3HZspTYReemt27E .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z3HZspTYReemt27E .cluster text{fill:#333;}#mermaid-svg-z3HZspTYReemt27E .cluster span{color:#333;}#mermaid-svg-z3HZspTYReemt27E div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z3HZspTYReemt27E .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z3HZspTYReemt27E rect.text{fill:none;stroke-width:0;}#mermaid-svg-z3HZspTYReemt27E .icon-shape,#mermaid-svg-z3HZspTYReemt27E .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z3HZspTYReemt27E .icon-shape p,#mermaid-svg-z3HZspTYReemt27E .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z3HZspTYReemt27E .icon-shape .label rect,#mermaid-svg-z3HZspTYReemt27E .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z3HZspTYReemt27E .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z3HZspTYReemt27E .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z3HZspTYReemt27E :root{&#8211;mermaid-font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;}<\/p>\n<p>         <span class=\"edgeLabel\"><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Language: monitor syntaxsynchronized \/ wait \/ notify<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Runtime: HotSpotmark word &#043; ObjectMonitor<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>User-space thread library: glibc NPTLpthread_mutex \/ pthread_cond<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Kernel: futex syscallwait queues &#043; scheduler<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Hardware: atomic instructionsLOCK CMPXCHG \/ LL-SC&#043; memory barriers<\/p>\n<p><\/span><\/p>\n<p>This diagram answers: when a synchronized block blocks, which layers does the request pass through?<\/p>\n<h3>Part 1: Monitors at the operating-system level<\/h3>\n<p>First, a clarification that removes a lot of confusion: a monitor is a language construct, not a kernel object. There is no monitor system call in Linux. What Hoare and Brinch Hansen proposed was a compiler that sees a monitor block, inserts a lock at every procedure entry and an unlock at every exit, and compiles wait\/signal into operations on condition variables. So \u201chow does the OS implement monitors?\u201d really means \u201cwhat primitives does the OS provide so that a compiler or runtime can assemble one?\u201d The answer is two things: locks and wait queues.<\/p>\n<h4>1.1 Mutual exclusion starts with one atomic instruction<\/h4>\n<p>On a uniprocessor, the only thing that can interrupt a critical section is an interrupt, so disabling interrupts on entry and re-enabling them on exit is enough. That stops working on multiple cores \u2014 you only silenced your core.<\/p>\n<p>On a multiprocessor, you need hardware that performs read-modify-write indivisibly:<\/p>\n<table>\n<tr>FamilyExampleSemantics<\/tr>\n<tbody>\n<tr>\n<td>TAS \/ exchange<\/td>\n<td>x86 XCHG<\/td>\n<td>Atomically store a new value, return the old one<\/td>\n<\/tr>\n<tr>\n<td>CAS<\/td>\n<td>x86 LOCK CMPXCHG<\/td>\n<td>Store only if the current value equals the expected one; report success<\/td>\n<\/tr>\n<tr>\n<td>LL\/SC<\/td>\n<td>ARM LDXR\/STXR (or ARMv8.1 CAS)<\/td>\n<td>Store only if nobody wrote the location since the load<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The LOCK prefix makes the core take exclusive ownership of the cache line in the coherence protocol, so no other core can modify it mid-operation; these instructions also act as memory barriers, so loads and stores inside the critical section cannot be reordered outside it. With that, the simplest lock \u2014 a spinlock \u2014 is a few lines:<\/p>\n<p><span class=\"token keyword\">void<\/span> <span class=\"token function\">spin_lock<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">int<\/span> <span class=\"token operator\">*<\/span>l<span class=\"token punctuation\">)<\/span>   <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">while<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token function\">xchg<\/span><span class=\"token punctuation\">(<\/span>l<span class=\"token punctuation\">,<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">&#061;&#061;<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token function\">cpu_relax<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <span class=\"token punctuation\">}<\/span>  <span class=\"token comment\">\/\/ retry until we win<\/span><br \/>\n<span class=\"token keyword\">void<\/span> <span class=\"token function\">spin_unlock<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">int<\/span> <span class=\"token operator\">*<\/span>l<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token function\">store_release<\/span><span class=\"token punctuation\">(<\/span>l<span class=\"token punctuation\">,<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <span class=\"token punctuation\">}<\/span><\/p>\n<p>A spinlock solves mutual exclusion but not blocking: the loser burns CPU the whole time. For hold times of tens of nanoseconds that is actually the fastest option; for long hold times, or when the holder has been descheduled, it is pure waste. So we need the second ingredient.<\/p>\n<h4>1.2 Blocking and waking: kernel wait queues and the scheduler<\/h4>\n<p>The kernel \u201cstops\u201d a thread by marking it as sleeping, removing it from the run queue and switching to someone else. The canonical Linux pattern for waiting on a condition is:<\/p>\n<p><span class=\"token function\">DEFINE_WAIT<\/span><span class=\"token punctuation\">(<\/span>wait<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>\n<span class=\"token keyword\">for<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">;<\/span><span class=\"token punctuation\">;<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n    <span class=\"token function\">prepare_to_wait<\/span><span class=\"token punctuation\">(<\/span><span class=\"token operator\">&amp;<\/span>wq<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>wait<span class=\"token punctuation\">,<\/span> TASK_INTERRUPTIBLE<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <span class=\"token comment\">\/\/ (1) enqueue and mark ourselves asleep<\/span><br \/>\n    <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span>condition<span class=\"token punctuation\">)<\/span>                                   <span class=\"token comment\">\/\/ (2) only then test the condition<\/span><br \/>\n        <span class=\"token keyword\">break<\/span><span class=\"token punctuation\">;<\/span><br \/>\n    <span class=\"token function\">schedule<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>                                      <span class=\"token comment\">\/\/ (3) give up the CPU<\/span><br \/>\n<span class=\"token punctuation\">}<\/span><br \/>\n<span class=\"token function\">finish_wait<\/span><span class=\"token punctuation\">(<\/span><span class=\"token operator\">&amp;<\/span>wq<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>wait<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/p>\n<p>And the waker:<\/p>\n<p>condition <span class=\"token operator\">&#061;<\/span> true<span class=\"token punctuation\">;<\/span><br \/>\n<span class=\"token function\">wake_up<\/span><span class=\"token punctuation\">(<\/span><span class=\"token operator\">&amp;<\/span>wq<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>   <span class=\"token comment\">\/\/ mark queued tasks TASK_RUNNING and put them back on a run queue<\/span><\/p>\n<p>Why no lost wake-up? Because of the order of (1) and (2). If the waker runs wake_up between (2) and (3), it flips our state back to TASK_RUNNING, so schedule() in (3) sees a runnable task and returns at once. Test the condition before enqueueing and that window would swallow the wake-up.<\/p>\n<p>What suspend and resume physically mean:<\/p>\n<ul>\n<li>Suspend: schedule() takes the task off the run queue; context_switch() saves registers and switches kernel stacks (and page tables if the next task is in another process). The thread now consumes no CPU at all.<\/li>\n<li>Resume: try_to_wake_up() sets the task to TASK_RUNNING and puts it on some CPU\u2019s run queue; if that CPU is idle, an IPI (Inter-Processor Interrupt) kicks it.<\/li>\n<\/ul>\n<p>A wake-up only makes the thread runnable; the scheduler decides when it actually runs. That is the physical origin of Mesa semantics: by the time the woken thread executes, someone else may have changed the condition again, so it must re-check in a while loop.<\/p>\n<h4>1.3 The kernel\u2019s own sleeping lock: spin, then queue<\/h4>\n<p>The kernel\u2019s struct mutex combines both ingredients into three paths:<\/p>\n<table>\n<tr>PathWhenWhat happens<\/tr>\n<tbody>\n<tr>\n<td>Fast path<\/td>\n<td>Lock is free<\/td>\n<td>One cmpxchg sets owner from 0 to us \u2014 done<\/td>\n<\/tr>\n<tr>\n<td>Mid path<\/td>\n<td>Lock is held, but the owner is currently running on a CPU<\/td>\n<td>Optimistic spinning (queued MCS-style) on the bet that it releases soon<\/td>\n<\/tr>\n<tr>\n<td>Slow path<\/td>\n<td>Owner is asleep, or spinning took too long<\/td>\n<td>Join wait_list, schedule(); the unlocker wakes us<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Keep this \u201cCAS first, spin briefly, then queue and sleep\u201d shape in mind. It reappears in futexes, in glibc and in HotSpot.<\/p>\n<h4>1.4 Assembling a monitor: the classic Hoare monitor built from semaphores<\/h4>\n<p>Given locks and wait queues \u2014 which is all a semaphore is \u2014 a compiler can translate a monitor mechanically. This is the textbook translation, and it implements Hoare semantics (the signalled thread runs immediately):<\/p>\n<p>semaphore mutex &#061; 1;       \/\/ monitor entry lock<br \/>\nsemaphore next  &#061; 0;       \/\/ signallers that stepped aside sleep here<br \/>\nint next_count  &#061; 0;       \/\/ how many are sleeping on next<\/p>\n<p>for each condition variable x:<br \/>\nsemaphore x_sem   &#061; 0;<br \/>\nint       x_count &#061; 0;<\/p>\n<p>procedure body:<br \/>\n    P(mutex);<br \/>\n    &#8230;body&#8230;<br \/>\n    if (next_count &gt; 0) V(next);   \/\/ hand the monitor back to a waiting signaller first<br \/>\n    else                V(mutex);<\/p>\n<p>x.wait():<br \/>\n    x_count&#043;&#043;;<br \/>\n    if (next_count &gt; 0) V(next); else V(mutex);   \/\/ leave the monitor<br \/>\n    P(x_sem);                                     \/\/ sleep on x<br \/>\n    x_count&#8211;;<\/p>\n<p>x.signal():<br \/>\n    if (x_count &gt; 0) {<br \/>\n        next_count&#043;&#043;;<br \/>\n        V(x_sem);      \/\/ wake one waiter on x and hand it the monitor<br \/>\n        P(next);       \/\/ step aside and sleep on next<br \/>\n        next_count&#8211;;<br \/>\n    }<\/p>\n<p>Reading it makes Hoare\u2019s cost obvious: every signal costs an extra context switch \u2014 the signaller sleeps so the signalled thread can run. Mesa semantics shrink x.signal() to \u201cV(x_sem) and keep going\u201d, saving that switch at the price of waiters re-checking their condition. That is the engineering reason both pthreads and Java chose Mesa.<\/p>\n<h4>1.5 The user-space shortcut: futex<\/h4>\n<p>The kernel machinery above has one drawback: even an uncontended lock would trap into the kernel on every acquire. A system call costs far more than an atomic instruction \u2014 typically hundreds of nanoseconds or more, and microseconds once a context switch is involved, versus nanoseconds for an uncontended CAS. The futex (Fast Userspace muTEX, merged in Linux 2.5 in 2002) fixes this by keeping the lock word in user memory and entering the kernel only when someone must actually sleep or be woken.<\/p>\n<p>A futex has two core operations:<\/p>\n<ul>\n<li>futex_wait(addr, expected) \u2014 the kernel, while holding the lock on the hash bucket for addr, checks *addr &#061;&#061; expected; only if it still matches does the caller go to sleep. Otherwise it returns EAGAIN immediately.<\/li>\n<li>futex_wake(addr, n) \u2014 wake up to n threads sleeping on addr.<\/li>\n<\/ul>\n<p>\u201cCheck the value\u201d and \u201cgo to sleep\u201d are atomic inside the kernel. It is the same anti-lost-wake-up trick as in 1.2, with the condition replaced by a user-space integer.<\/p>\n<p>Ulrich Drepper\u2019s Futexes Are Tricky gives the classic mutex built on it, using three states \u2014 0 free, 1 locked with no waiters, 2 locked with possible waiters:<\/p>\n<p><span class=\"token keyword\">void<\/span> <span class=\"token function\">lock<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">int<\/span> <span class=\"token operator\">*<\/span>f<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n    <span class=\"token keyword\">int<\/span> c <span class=\"token operator\">&#061;<\/span> <span class=\"token function\">cmpxchg<\/span><span class=\"token punctuation\">(<\/span>f<span class=\"token punctuation\">,<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>           <span class=\"token comment\">\/\/ fast path: 0 -&gt; 1, pure user space<\/span><br \/>\n    <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span>c <span class=\"token operator\">!&#061;<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n        <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span>c <span class=\"token operator\">!&#061;<\/span> <span class=\"token number\">2<\/span><span class=\"token punctuation\">)<\/span> c <span class=\"token operator\">&#061;<\/span> <span class=\"token function\">xchg<\/span><span class=\"token punctuation\">(<\/span>f<span class=\"token punctuation\">,<\/span> <span class=\"token number\">2<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>     <span class=\"token comment\">\/\/ announce &#034;someone is waiting&#034;<\/span><br \/>\n        <span class=\"token keyword\">while<\/span> <span class=\"token punctuation\">(<\/span>c <span class=\"token operator\">!&#061;<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n            <span class=\"token function\">futex_wait<\/span><span class=\"token punctuation\">(<\/span>f<span class=\"token punctuation\">,<\/span> <span class=\"token number\">2<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>           <span class=\"token comment\">\/\/ slow path: sleep in the kernel<\/span><br \/>\n            c <span class=\"token operator\">&#061;<\/span> <span class=\"token function\">xchg<\/span><span class=\"token punctuation\">(<\/span>f<span class=\"token punctuation\">,<\/span> <span class=\"token number\">2<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>\n        <span class=\"token punctuation\">}<\/span><br \/>\n    <span class=\"token punctuation\">}<\/span><br \/>\n<span class=\"token punctuation\">}<\/span><\/p>\n<p><span class=\"token keyword\">void<\/span> <span class=\"token function\">unlock<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">int<\/span> <span class=\"token operator\">*<\/span>f<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n    <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token function\">atomic_dec<\/span><span class=\"token punctuation\">(<\/span>f<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">!&#061;<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>           <span class=\"token comment\">\/\/ was 2: there may be waiters<\/span><br \/>\n        <span class=\"token operator\">*<\/span>f <span class=\"token operator\">&#061;<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">;<\/span><br \/>\n        <span class=\"token function\">futex_wake<\/span><span class=\"token punctuation\">(<\/span>f<span class=\"token punctuation\">,<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>               <span class=\"token comment\">\/\/ enter the kernel to wake one<\/span><br \/>\n    <span class=\"token punctuation\">}<\/span>                                   <span class=\"token comment\">\/\/ was 1: nobody waiting, no syscall<\/span><br \/>\n<span class=\"token punctuation\">}<\/span><\/p>\n<p>     Thread B<\/p>\n<p>     Kernel futex<\/p>\n<p>     User memory f<\/p>\n<p>     Thread A<\/p>\n<p>      Thread B<\/p>\n<p>      Kernel futex<\/p>\n<p>      User memory f<\/p>\n<p>      Thread A<\/p>\n<p>  #mermaid-svg-vs855b2IBAolg4II{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vs855b2IBAolg4II .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vs855b2IBAolg4II .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vs855b2IBAolg4II .error-icon{fill:#552222;}#mermaid-svg-vs855b2IBAolg4II .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vs855b2IBAolg4II .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vs855b2IBAolg4II .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vs855b2IBAolg4II .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vs855b2IBAolg4II .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vs855b2IBAolg4II .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vs855b2IBAolg4II .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vs855b2IBAolg4II .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vs855b2IBAolg4II .marker.cross{stroke:#333333;}#mermaid-svg-vs855b2IBAolg4II svg{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vs855b2IBAolg4II p{margin:0;}#mermaid-svg-vs855b2IBAolg4II .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vs855b2IBAolg4II text.actor&gt;tspan{fill:black;stroke:none;}#mermaid-svg-vs855b2IBAolg4II .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vs855b2IBAolg4II .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-vs855b2IBAolg4II .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-vs855b2IBAolg4II .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-vs855b2IBAolg4II #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-vs855b2IBAolg4II .sequenceNumber{fill:white;}#mermaid-svg-vs855b2IBAolg4II #sequencenumber{fill:#333;}#mermaid-svg-vs855b2IBAolg4II #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-vs855b2IBAolg4II .messageText{fill:#333;stroke:none;}#mermaid-svg-vs855b2IBAolg4II .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vs855b2IBAolg4II .labelText,#mermaid-svg-vs855b2IBAolg4II .labelText&gt;tspan{fill:black;stroke:none;}#mermaid-svg-vs855b2IBAolg4II .loopText,#mermaid-svg-vs855b2IBAolg4II .loopText&gt;tspan{fill:black;stroke:none;}#mermaid-svg-vs855b2IBAolg4II .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vs855b2IBAolg4II .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-vs855b2IBAolg4II .noteText,#mermaid-svg-vs855b2IBAolg4II .noteText&gt;tspan{fill:black;stroke:none;}#mermaid-svg-vs855b2IBAolg4II .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vs855b2IBAolg4II .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vs855b2IBAolg4II .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vs855b2IBAolg4II .actorPopupMenu{position:absolute;}#mermaid-svg-vs855b2IBAolg4II .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 \/ 0.4));}#mermaid-svg-vs855b2IBAolg4II .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vs855b2IBAolg4II .actor-man circle,#mermaid-svg-vs855b2IBAolg4II line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-vs855b2IBAolg4II :root{&#8211;mermaid-font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;}<\/p>\n<p>   cmpxchg 0\u21921 succeeds (no syscall)<\/p>\n<p>   cmpxchg 0\u21921 fails, xchg sets 2<\/p>\n<p>   futex_wait(f, 2)<\/p>\n<p>   checks *f &#061;&#061; 2, queues B, B sleeps<\/p>\n<p>   atomic_dec 2\u21921: waiters exist<\/p>\n<p>   *f &#061; 0<\/p>\n<p>   futex_wake(f, 1)<\/p>\n<p>   B back on a run queue<\/p>\n<p>   xchg sets 2, B owns the lock<\/p>\n<p>This diagram answers: at which moments does a futex lock actually enter the kernel?<\/p>\n<p>Bottom line: an uncontended lock and unlock never leave user space; the user\/kernel transition happens only when a thread must really sleep or really be woken. That is the physical reason behind the folk wisdom \u201clocks are cheap, contention is expensive.\u201d<\/p>\n<h4>1.6 Condition variables: the skeleton of pthread_cond<\/h4>\n<p>The hard part of a condition variable is that wait must atomically \u201crelease the mutex and sleep\u201d. A simplified futex version:<\/p>\n<p><span class=\"token keyword\">void<\/span> <span class=\"token function\">cond_wait<\/span><span class=\"token punctuation\">(<\/span><span class=\"token class-name\">cond_t<\/span> <span class=\"token operator\">*<\/span>cv<span class=\"token punctuation\">,<\/span> <span class=\"token class-name\">mutex_t<\/span> <span class=\"token operator\">*<\/span>m<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n    <span class=\"token keyword\">int<\/span> seq <span class=\"token operator\">&#061;<\/span> cv<span class=\"token operator\">-&gt;<\/span>seq<span class=\"token punctuation\">;<\/span>          <span class=\"token comment\">\/\/ (1) remember the current signal sequence<\/span><br \/>\n    <span class=\"token function\">mutex_unlock<\/span><span class=\"token punctuation\">(<\/span>m<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>            <span class=\"token comment\">\/\/ (2) release the mutex<\/span><br \/>\n    <span class=\"token function\">futex_wait<\/span><span class=\"token punctuation\">(<\/span><span class=\"token operator\">&amp;<\/span>cv<span class=\"token operator\">-&gt;<\/span>seq<span class=\"token punctuation\">,<\/span> seq<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>  <span class=\"token comment\">\/\/ (3) sleep only if no signal arrived since (1)<\/span><br \/>\n    <span class=\"token function\">mutex_lock<\/span><span class=\"token punctuation\">(<\/span>m<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>              <span class=\"token comment\">\/\/ (4) compete for the mutex again (Mesa)<\/span><br \/>\n<span class=\"token punctuation\">}<\/span><\/p>\n<p><span class=\"token keyword\">void<\/span> <span class=\"token function\">cond_signal<\/span><span class=\"token punctuation\">(<\/span><span class=\"token class-name\">cond_t<\/span> <span class=\"token operator\">*<\/span>cv<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>\n    <span class=\"token function\">atomic_inc<\/span><span class=\"token punctuation\">(<\/span><span class=\"token operator\">&amp;<\/span>cv<span class=\"token operator\">-&gt;<\/span>seq<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>       <span class=\"token comment\">\/\/ bump the sequence<\/span><br \/>\n    <span class=\"token function\">futex_wake<\/span><span class=\"token punctuation\">(<\/span><span class=\"token operator\">&amp;<\/span>cv<span class=\"token operator\">-&gt;<\/span>seq<span class=\"token punctuation\">,<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>    <span class=\"token comment\">\/\/ wake one sleeper<\/span><br \/>\n<span class=\"token punctuation\">}<\/span><\/p>\n<p>If a signal lands between (2) and (3), the sequence has already changed and futex_wait returns immediately \u2014 nothing is lost. Step (4) is Mesa semantics in code: the woken thread must re-acquire the mutex, so callers must write while (!cond) pthread_cond_wait(&#8230;).<\/p>\n<p>Real glibc (the algorithm introduced in 2.25) is considerably more involved: it splits waiters into two groups so a signal can never wake a thread that started waiting after the signal, and it handles sequence wrap-around and cancellation. The four steps above are still its skeleton.<\/p>\n<h3>Part 2: Monitors in Java<\/h3>\n<p>At the language level every Java object is a monitor. Physically, almost none of them carries one \u2014 the memory cost would be prohibitive. HotSpot\u2019s trick is to squeeze lock state into a few bits of the 64-bit object header, and allocate a real ObjectMonitor only when threads genuinely need to queue.<\/p>\n<h4>2.1 The bytecode entry points: monitorenter \/ monitorexit<\/h4>\n<p>A synchronized block compiles to a monitorenter \/ monitorexit pair, and javac also emits an exception handler that runs monitorexit if the block throws \u2014 that is \u201cthe compiler releases the lock for you\u201d, implemented. A synchronized method gets no such bytecodes; instead its ACC_SYNCHRONIZED flag tells the JVM to lock on invocation and unlock on return. The monitor-theory promise that \u201clocking is inserted by the compiler, so you cannot forget it\u201d is delivered literally.<\/p>\n<h4>2.2 The mark word in the object header<\/h4>\n<p>On 64-bit HotSpot, the first word of every object header is the mark word. Its two lowest bits (plus, historically, one biased bit) decide what the remaining bits mean. The classic layout, without compact object headers:<\/p>\n<table>\n<tr>Lock stateHigh bitsBiased bitLock bits (lowest 2)<\/tr>\n<tbody>\n<tr>\n<td>Unlocked<\/td>\n<td>25 unused \u00b7 31 identity hash \u00b7 1 unused \u00b7 4 GC age<\/td>\n<td>0<\/td>\n<td>01<\/td>\n<\/tr>\n<tr>\n<td>Biased (removed)<\/td>\n<td>54 owning-thread pointer \u00b7 2 epoch \u00b7 1 unused \u00b7 4 GC age<\/td>\n<td>1<\/td>\n<td>01<\/td>\n<\/tr>\n<tr>\n<td>Lightweight (legacy)<\/td>\n<td>62-bit pointer to an on-stack lock record<\/td>\n<td>\u2014<\/td>\n<td>00<\/td>\n<\/tr>\n<tr>\n<td>Heavyweight<\/td>\n<td>62-bit pointer to an ObjectMonitor<\/td>\n<td>\u2014<\/td>\n<td>10<\/td>\n<\/tr>\n<tr>\n<td>GC marked<\/td>\n<td>used by the collector<\/td>\n<td>\u2014<\/td>\n<td>11<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Two things to notice:<\/p>\n<ul>\n<li>Because one word is reused for everything, locking displaces the identity hash and GC age, which then have to be parked elsewhere \u2014 in the stack lock record\u2019s \u201cdisplaced header\u201d (legacy lightweight locking) or inside the ObjectMonitor (heavyweight).<\/li>\n<li>Compact object headers (experimental in JDK 24 via JEP 450, a product feature in JDK 25 via JEP 519) shrink the header to 64 bits and change this layout; they rely on the newer lightweight locking and the \u201cobject monitor table\u201d described below. The table above is the classic version, best for understanding the idea.<\/li>\n<\/ul>\n<h4>2.3 Lock states and the upgrade path<\/h4>\n<p>  #mermaid-svg-9QFzQjnCZrBgcg1w{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9QFzQjnCZrBgcg1w .error-icon{fill:#552222;}#mermaid-svg-9QFzQjnCZrBgcg1w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9QFzQjnCZrBgcg1w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9QFzQjnCZrBgcg1w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9QFzQjnCZrBgcg1w .marker.cross{stroke:#333333;}#mermaid-svg-9QFzQjnCZrBgcg1w svg{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9QFzQjnCZrBgcg1w p{margin:0;}#mermaid-svg-9QFzQjnCZrBgcg1w defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-9QFzQjnCZrBgcg1w g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-9QFzQjnCZrBgcg1w g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-9QFzQjnCZrBgcg1w g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-9QFzQjnCZrBgcg1w g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-9QFzQjnCZrBgcg1w g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-9QFzQjnCZrBgcg1w .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-9QFzQjnCZrBgcg1w .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-9QFzQjnCZrBgcg1w .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-9QFzQjnCZrBgcg1w .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9QFzQjnCZrBgcg1w .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-9QFzQjnCZrBgcg1w .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-9QFzQjnCZrBgcg1w .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-9QFzQjnCZrBgcg1w .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9QFzQjnCZrBgcg1w .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9QFzQjnCZrBgcg1w .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9QFzQjnCZrBgcg1w .edgeLabel .label text{fill:#333;}#mermaid-svg-9QFzQjnCZrBgcg1w .label div .edgeLabel{color:#333;}#mermaid-svg-9QFzQjnCZrBgcg1w .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-9QFzQjnCZrBgcg1w .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-9QFzQjnCZrBgcg1w .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-9QFzQjnCZrBgcg1w .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-9QFzQjnCZrBgcg1w .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-9QFzQjnCZrBgcg1w .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9QFzQjnCZrBgcg1w .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9QFzQjnCZrBgcg1w #statediagram-barbEnd{fill:#333333;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9QFzQjnCZrBgcg1w .cluster-label,#mermaid-svg-9QFzQjnCZrBgcg1w .nodeLabel{color:#131300;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-9QFzQjnCZrBgcg1w .note-edge{stroke-dasharray:5;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-note text{fill:black;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram-note .nodeLabel{color:black;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagram .edgeLabel{color:red;}#mermaid-svg-9QFzQjnCZrBgcg1w #dependencyStart,#mermaid-svg-9QFzQjnCZrBgcg1w #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-9QFzQjnCZrBgcg1w .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9QFzQjnCZrBgcg1w :root{&#8211;mermaid-font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;}<\/p>\n<p>         <span class=\"edgeLabel\"><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/p>\n<p>CAS on lock bits succeeds<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/p>\n<p>CAS restores on exit<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/p>\n<p>contentionor wait() called<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/p>\n<p>direct contentionor wait() called<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"edgeLabel\"><\/p>\n<p>idle monitordeflated asynchronously<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Unlocked<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Lightweight<\/p>\n<p><\/span><\/p>\n<p>         <span class=\"nodeLabel\"><\/p>\n<p>Heavyweight<\/p>\n<p><\/span><\/p>\n<p>This diagram answers: how does an object\u2019s lock state move between unlocked, lightweight and heavyweight? (Biased locking is gone and is not shown.)<\/p>\n<p>Biased locking (historical). The observation behind it: most locks are only ever taken by one thread. The first acquire CASes the thread ID into the mark word; later re-entries by the same thread only compare IDs \u2014 no CAS at all. The bill arrives on revocation: when another thread shows up, the owner must be stopped at a safepoint and its stack inspected to see whether it still holds the lock. As uncontended CAS became cheap on modern CPUs, while revocation stayed expensive and the code stayed complex, biased locking was disabled by default and deprecated in JDK 15 (JEP 374), and its flags were obsoleted and the implementation removed in JDK 18. The popular \u201cbiased \u2192 lightweight \u2192 heavyweight\u201d upgrade story describes the default behaviour before JDK 15.<\/p>\n<p>Lightweight locking, legacy (\u201cstack locking\u201d). The thread allocates a lock record in the current stack frame, copies the object\u2019s original mark word into it (the displaced header), then CASes the mark word to point at that record with lock bits 00. A recursive acquire just pushes another lock record with an empty displaced header. Unlock CASes the displaced header back. Only user-space CAS, never a system call.<\/p>\n<p>Lightweight locking, new (lock-stack). Introduced in JDK 21 and the default from JDK 23 (LockingMode defaults to lightweight). Each thread owns a small array, the lock-stack. Locking CASes only the mark word\u2019s lock bits from 01 to 00 and pushes the object reference onto the thread\u2019s lock-stack; the hash and age bits stay where they are, and there is no pointer into anyone\u2019s stack. \u201cDo I own this?\u201d becomes a scan of your own lock-stack. Part of the motivation is to make room for compact object headers and virtual threads, both of which are awkward when the header points into a specific thread\u2019s stack.<\/p>\n<p>Inflation to heavyweight. Triggered when:<\/p>\n<ul>\n<li>the lightweight CAS fails (contention);<\/li>\n<li>the owner calls wait() \u2014 lightweight locks have no wait set, so a real ObjectMonitor is needed;<\/li>\n<li>the lock-stack is full, or another implementation limit is hit.<\/li>\n<\/ul>\n<p>Inflation allocates (or reuses) an ObjectMonitor and points the mark word at it with lock bits 10. From JDK 24, -XX:&#043;UseObjectMonitorTable can instead keep the object-to-monitor mapping in a side hash table so the mark word is not overwritten \u2014 a capability compact object headers require.<\/p>\n<p>Do locks ever downgrade? The common claim is \u201clocks only upgrade, never downgrade.\u201d Within a single holding period that is roughly true, but an ObjectMonitor is not welded to its object: since JDK 15 a dedicated thread deflates idle monitors asynchronously, returning the object to the unlocked state and recycling the monitor. The precise statement is: no downgrade while held; deflation once idle.<\/p>\n<h4>2.4 ObjectMonitor: the monitor in full<\/h4>\n<p>ObjectMonitor is a C&#043;&#043; object \u2014 the textbook monitor made concrete inside HotSpot. Key fields in the classic version (roughly JDK 8 through 21):<\/p>\n<table>\n<tr>FieldRoleMonitor concept<\/tr>\n<tbody>\n<tr>\n<td>_owner<\/td>\n<td>Current owner<\/td>\n<td>The one thread inside<\/td>\n<\/tr>\n<tr>\n<td>_recursions<\/td>\n<td>Re-entry count<\/td>\n<td>Reentrancy<\/td>\n<\/tr>\n<tr>\n<td>_cxq<\/td>\n<td>Singly linked list of new contenders, pushed by CAS (LIFO)<\/td>\n<td>Entry queue (lock-free part)<\/td>\n<\/tr>\n<tr>\n<td>_EntryList<\/td>\n<td>Doubly linked list from which the releasing thread picks a successor<\/td>\n<td>Entry queue<\/td>\n<\/tr>\n<tr>\n<td>_WaitSet<\/td>\n<td>Circular doubly linked list of threads in wait()<\/td>\n<td>Condition-variable queue<\/td>\n<\/tr>\n<tr>\n<td>_succ<\/td>\n<td>Thread already chosen as the presumed successor<\/td>\n<td>Avoids redundant wake-ups<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Newer JDKs have refactored these structures \u2014 for example, JEP 491 changed how the owner is recorded (a thread ID rather than a platform-thread pointer) so virtual threads can own monitors, and the entry queues have been consolidated \u2014 so field names may differ. The shape \u2014 owner, recursion count, entry queue, wait set \u2014 has not changed.<\/p>\n<p>Acquire (enter) is the familiar three-stage pattern again:<\/p>\n<li>CAS the owner from empty to self; on success, return. If the owner is already self, _recursions&#043;&#043;.<\/li>\n<li>Adaptive spinning, sized by how often spinning on this monitor has paid off before \u2014 betting the owner releases soon.<\/li>\n<li>Queue and sleep: wrap self in an ObjectWaiter node, CAS-push it onto _cxq, then loop \u201ctry once more, otherwise park\u201d.<\/li>\n<p>Release (exit): if _recursions is non-zero, decrement and return. Otherwise release the owner (with a memory barrier). With no waiters, return \u2014 no system call on this path either. With waiters, pick a successor from _EntryList (draining _cxq into it first if it is empty) and unpark just that one, avoiding a thundering herd.<\/p>\n<p>Notice that step 3 wakes up to try the CAS again, not to receive the lock. The releasing thread only wakes a successor; the lock itself is left free for anyone to grab, and a newly arriving thread may CAS it before the successor is even scheduled (barging). That buys synchronized throughput, but it means synchronized is not fair \u2014 if you need FIFO, use new ReentrantLock(true).<\/p>\n<h4>2.5 What wait \/ notify do inside ObjectMonitor<\/h4>\n<p>     Thread T2<\/p>\n<p>     ObjectMonitor<\/p>\n<p>     Thread T1<\/p>\n<p>      Thread T2<\/p>\n<p>      ObjectMonitor<\/p>\n<p>      Thread T1<\/p>\n<p>  #mermaid-svg-wMcocsmERtPIF1tm{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wMcocsmERtPIF1tm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wMcocsmERtPIF1tm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wMcocsmERtPIF1tm .error-icon{fill:#552222;}#mermaid-svg-wMcocsmERtPIF1tm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wMcocsmERtPIF1tm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wMcocsmERtPIF1tm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wMcocsmERtPIF1tm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wMcocsmERtPIF1tm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wMcocsmERtPIF1tm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wMcocsmERtPIF1tm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wMcocsmERtPIF1tm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wMcocsmERtPIF1tm .marker.cross{stroke:#333333;}#mermaid-svg-wMcocsmERtPIF1tm svg{font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wMcocsmERtPIF1tm p{margin:0;}#mermaid-svg-wMcocsmERtPIF1tm .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wMcocsmERtPIF1tm text.actor&gt;tspan{fill:black;stroke:none;}#mermaid-svg-wMcocsmERtPIF1tm .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wMcocsmERtPIF1tm .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-wMcocsmERtPIF1tm .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wMcocsmERtPIF1tm .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wMcocsmERtPIF1tm #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wMcocsmERtPIF1tm .sequenceNumber{fill:white;}#mermaid-svg-wMcocsmERtPIF1tm #sequencenumber{fill:#333;}#mermaid-svg-wMcocsmERtPIF1tm #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wMcocsmERtPIF1tm .messageText{fill:#333;stroke:none;}#mermaid-svg-wMcocsmERtPIF1tm .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wMcocsmERtPIF1tm .labelText,#mermaid-svg-wMcocsmERtPIF1tm .labelText&gt;tspan{fill:black;stroke:none;}#mermaid-svg-wMcocsmERtPIF1tm .loopText,#mermaid-svg-wMcocsmERtPIF1tm .loopText&gt;tspan{fill:black;stroke:none;}#mermaid-svg-wMcocsmERtPIF1tm .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wMcocsmERtPIF1tm .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wMcocsmERtPIF1tm .noteText,#mermaid-svg-wMcocsmERtPIF1tm .noteText&gt;tspan{fill:black;stroke:none;}#mermaid-svg-wMcocsmERtPIF1tm .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wMcocsmERtPIF1tm .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wMcocsmERtPIF1tm .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wMcocsmERtPIF1tm .actorPopupMenu{position:absolute;}#mermaid-svg-wMcocsmERtPIF1tm .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 \/ 0.4));}#mermaid-svg-wMcocsmERtPIF1tm .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wMcocsmERtPIF1tm .actor-man circle,#mermaid-svg-wMcocsmERtPIF1tm line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wMcocsmERtPIF1tm :root{&#8211;mermaid-font-family:\\&#8221;trebuchet ms\\&#8221;,verdana,arial,sans-serif;}<\/p>\n<p>   holds lock, calls wait()<\/p>\n<p>   T1 joins _WaitSet<\/p>\n<p>   saves recursions, fully releases<\/p>\n<p>   parks<\/p>\n<p>   acquires lock, changes state<\/p>\n<p>   notify()<\/p>\n<p>   T1 moved from _WaitSet<\/p>\n<p>   to entry queue (still asleep)<\/p>\n<p>   leaves synchronized, releases<\/p>\n<p>   T1 chosen as successor, unparked<\/p>\n<p>   re-acquires lock, restores recursions<\/p>\n<p>   re-checks condition in while loop<\/p>\n<p>This diagram answers: which ObjectMonitor queues does a thread move through during wait\/notify, and when is it actually woken?<\/p>\n<p>Facts you can read straight off the implementation:<\/p>\n<ul>\n<li>wait() requires ownership. Its first step checks that _owner is the caller and throws IllegalMonitorStateException otherwise.<\/li>\n<li>wait() releases all re-entries. It saves _recursions, zeroes it, exits completely, and restores the count after re-acquiring \u2014 which is why calling wait() three synchronized levels deep does not deadlock.<\/li>\n<li>notify() wakes nobody immediately. It only moves a waiter from _WaitSet to the entry queue. The notified thread is unparked later, once the notifier has left the critical section and the waiter has been chosen as successor. That is Mesa\u2019s signal-and-continue, visible in the source.<\/li>\n<li>There is only one _WaitSet, so threads waiting for different conditions share it \u2014 hence the default advice to use notifyAll(). For several conditions, use ReentrantLock with multiple Conditions; each has its own wait queue.<\/li>\n<\/ul>\n<h4>2.6 How the JVM reaches the OS primitives<\/h4>\n<p>HotSpot does not issue futex system calls directly; it goes through a platform layer:<\/p>\n<table>\n<tr>Java levelInside HotSpotOn Linux it becomes<\/tr>\n<tbody>\n<tr>\n<td>synchronized contention \/ wait()<\/td>\n<td>ObjectMonitor \u2192 per-thread ParkEvent \u2192 os::PlatformEvent::park()<\/td>\n<td>pthread_mutex &#043; pthread_cond_wait<\/td>\n<\/tr>\n<tr>\n<td>LockSupport.park() (AQS, ReentrantLock, Condition)<\/td>\n<td>per-thread Parker<\/td>\n<td>pthread_mutex &#043; pthread_cond_(timed)wait<\/td>\n<\/tr>\n<tr>\n<td>Both<\/td>\n<td>glibc NPTL<\/td>\n<td>futex(FUTEX_WAIT \/ FUTEX_WAKE) system calls<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>PlatformEvent carries a small counter as its own fast path: if the unpark arrives before the park (the permit is already available), park returns without touching pthreads at all. Only a real sleep calls pthread_cond_wait, which glibc turns into futex_wait, which lands in the kernel wait queue and scheduler from Part 1. End to end:<\/p>\n<p>synchronized contention<br \/>\n  -&gt; CAS fails -&gt; spinning fails -&gt; enqueue on ObjectMonitor<br \/>\n  -&gt; ParkEvent::park -&gt; pthread_cond_wait<br \/>\n  -&gt; futex(FUTEX_WAIT) syscall             &lt;- first entry into kernel mode<br \/>\n  -&gt; kernel: join hash-bucket queue, schedule() away<br \/>\n&#8230;<br \/>\nowner exits -&gt; unpark successor -&gt; pthread_cond_signal<br \/>\n  -&gt; futex(FUTEX_WAKE)                      &lt;- the waker enters kernel mode<br \/>\n  -&gt; try_to_wake_up() -&gt; successor back on a run queue<\/p>\n<p>On macOS, pthreads sit on Darwin\u2019s psynch system calls rather than futexes, but the structure is the same.<\/p>\n<h4>2.7 Virtual threads: moving \u201csleep\u201d from the kernel into the JVM<\/h4>\n<p>A virtual thread (JEP 444, JDK 21) that blocks does not take the path to futex above. It saves its frames as a continuation on the heap and unmounts from its carrier platform thread, which goes off to run other virtual threads. To resume, the JVM\u2019s scheduler (a ForkJoinPool) mounts it again on some carrier.<\/p>\n<p>In JDK 21\u201323, blocking inside synchronized pinned the carrier \u2014 partly because the old implementation tied lock ownership to a particular platform thread and its stack. JEP 491 (JDK 24) reworked how ObjectMonitor records its owner, so synchronized now unmounts in most cases. Pinning still happens when there is a native frame on the stack and during class initialisation.<\/p>\n<h3>Part 3: The two layers side by side<\/h3>\n<table>\n<tr>MechanismOS \/ thread libraryHotSpot synchronized<\/tr>\n<tbody>\n<tr>\n<td>Atomicity of mutual exclusion<\/td>\n<td>Hardware atomics (LOCK CMPXCHG, LL\/SC); interrupt masking on a uniprocessor<\/td>\n<td>The same CAS, applied to the mark word or _owner<\/td>\n<\/tr>\n<tr>\n<td>Uncontended fast path<\/td>\n<td>futex lock: one user-space CAS, zero syscalls<\/td>\n<td>Lightweight lock: one CAS (biased locking used to skip even that)<\/td>\n<\/tr>\n<tr>\n<td>Brief contention<\/td>\n<td>Kernel mutex optimistic spinning; glibc adaptive mutexes optional<\/td>\n<td>ObjectMonitor adaptive spinning<\/td>\n<\/tr>\n<tr>\n<td>Entry queue<\/td>\n<td>Kernel wait_list \/ futex hash-bucket queue<\/td>\n<td>_cxq &#043; _EntryList<\/td>\n<\/tr>\n<tr>\n<td>Condition variable<\/td>\n<td>pthread_cond: sequence counter &#043; futex<\/td>\n<td>_WaitSet (only one per object)<\/td>\n<\/tr>\n<tr>\n<td>Lost wake-up prevention<\/td>\n<td>Enqueue or record a sequence first, test second, sleep last \u2014 checked atomically in the kernel<\/td>\n<td>Join _WaitSet before releasing the lock; ParkEvent permit counter<\/td>\n<\/tr>\n<tr>\n<td>Suspend<\/td>\n<td>Off the run queue &#043; context switch<\/td>\n<td>park \u2192 pthread_cond_wait \u2192 futex<\/td>\n<\/tr>\n<tr>\n<td>Resume<\/td>\n<td>Back on a run queue, IPI if needed<\/td>\n<td>unpark one successor \u2192 futex wake<\/td>\n<\/tr>\n<tr>\n<td>Signalling semantics<\/td>\n<td>Mesa (pthreads)<\/td>\n<td>Mesa<\/td>\n<\/tr>\n<tr>\n<td>Fairness<\/td>\n<td>Generally not guaranteed<\/td>\n<td>Not guaranteed; barging allowed<\/td>\n<\/tr>\n<tr>\n<td>When kernel mode is entered<\/td>\n<td>Only to really sleep or really wake<\/td>\n<td>Only after inflation, on a real park \/ unpark<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Part 4: What this adds to the companion article<\/h3>\n<table>\n<tr>TopicFrom PV Operations and Monitors\u2026This article<\/tr>\n<tbody>\n<tr>\n<td>Monitor definition, Hoare vs. Mesa<\/td>\n<td>Covered<\/td>\n<td>Adds the implementation cost of each (1.4)<\/td>\n<\/tr>\n<tr>\n<td>synchronized \/ wait \/ notify usage rules<\/td>\n<td>Covered<\/td>\n<td>Adds where each rule comes from inside ObjectMonitor (2.5)<\/td>\n<\/tr>\n<tr>\n<td>AQS, Condition, Semaphore<\/td>\n<td>Covered<\/td>\n<td>Only the shared park path to the OS (2.6)<\/td>\n<\/tr>\n<tr>\n<td>Atomic instructions, spinlocks, kernel wait queues, lost wake-up<\/td>\n<td>Not covered<\/td>\n<td>1.1\u20131.3<\/td>\n<\/tr>\n<tr>\n<td>futex fast\/slow paths, pthread_cond<\/td>\n<td>Named only<\/td>\n<td>1.5\u20131.6<\/td>\n<\/tr>\n<tr>\n<td>Mark word, lock upgrades, inflation and deflation<\/td>\n<td>One sentence<\/td>\n<td>2.2\u20132.3<\/td>\n<\/tr>\n<tr>\n<td>ObjectMonitor structure, barging and fairness<\/td>\n<td>Not covered<\/td>\n<td>2.4<\/td>\n<\/tr>\n<tr>\n<td>Virtual threads<\/td>\n<td>Covered (unmounting and pinning)<\/td>\n<td>Adds why pinning happened (2.7)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Two simplifications in the companion article have been corrected there:<\/p>\n<li>\u201cLockSupport.park\/unpark \u2192 futex\u201d: HotSpot does not call futex directly. Parker \/ PlatformEvent sit on glibc\u2019s pthread_mutex \/ pthread_cond, which reach futex on Linux.<\/li>\n<li>\u201cBiased locking was deprecated and disabled by default in JDK 15 via JEP 374\u201d: true but incomplete \u2014 the implementation was removed in JDK 18. The three-stage \u201cbiased \u2192 lightweight \u2192 heavyweight\u201d story therefore describes pre-JDK-15 defaults only.<\/li>\n<h3>Conclusions<\/h3>\n<ul>\n<li>A monitor is not a kernel object; it is a language construct assembled from locks and wait queues. The OS supplies atomic instructions, futexes, wait queues and a scheduler; the compiler and runtime turn them into implicit mutual exclusion plus condition variables.<\/li>\n<li>Every layer uses the same three stages: CAS first (pure user space), spin briefly (bet the owner releases soon), then queue and sleep (enter the kernel). The kernel mutex, futex locks, glibc and HotSpot\u2019s ObjectMonitor all follow it.<\/li>\n<li>There is one core trick against lost wake-ups: register your intent to wait (enqueue or record a sequence number) before testing the condition, then sleep \u2014 with the test-and-sleep done atomically in the kernel.<\/li>\n<li>User\/kernel transitions happen only under real contention. An uncontended synchronized is one CAS; only an inflated monitor that must actually park or unpark reaches the futex syscall via pthread_cond.<\/li>\n<li>Java\u2019s monitors materialise on demand. Normally a monitor is a couple of bits in the mark word; it inflates into an ObjectMonitor only for queueing or wait(), and is deflated asynchronously once idle. Biased locking is history, and since JDK 23 lightweight locking itself uses the lock-stack design.<\/li>\n<li>Mesa semantics have a physical cause: a kernel wake-up only makes a thread runnable, and notify() only moves a thread back to the entry queue. By the time it runs, the condition may have changed \u2014 so the while loop stays.<\/li>\n<\/ul>\n<h3>References<\/h3>\n<ul>\n<li>C. A. R. Hoare, \u201cMonitors: An Operating System Structuring Concept\u201d, Communications of the ACM, 1974 (including the semaphore-based construction).<\/li>\n<li>B. Lampson, D. Redell, \u201cExperience with Processes and Monitors in Mesa\u201d, Communications of the ACM, 1980.<\/li>\n<li>A. Silberschatz et al., Operating System Concepts, sections \u201cMonitors\u201d and \u201cImplementing a Monitor Using Semaphores\u201d.<\/li>\n<li>H. Franke, R. Russell, M. Kirkwood, \u201cFuss, Futexes and Furwocks: Fast Userlevel Locking in Linux\u201d, Ottawa Linux Symposium, 2002.<\/li>\n<li>U. Drepper, \u201cFutexes Are Tricky\u201d, 2011.<\/li>\n<li>Linux kernel documentation: Documentation\/locking\/mutex-design.rst; man 2 futex.<\/li>\n<li>OpenJDK HotSpot sources: src\/hotspot\/share\/runtime\/objectMonitor.{hpp,cpp}, synchronizer.cpp, lockStack.hpp, os\/posix\/os_posix.cpp (PlatformEvent, Parker).<\/li>\n<li>JEP 374 (Deprecate and Disable Biased Locking), JEP 444 (Virtual Threads), JEP 450 \/ JEP 519 (Compact Object Headers), JEP 491 (Synchronize Virtual Threads without Pinning).<\/li>\n<li>D. Dice, \u201cBiased Locking in HotSpot\u201d; OpenJDK Wiki, \u201cSynchronization\u201d.<\/li>\n<li>Companion article: From PV Operations and Monitors to Modern Java Concurrency.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>\u4e2d\u6587\u7248&#xff1a;\u7ba1\u7a0b\u662f\u600e\u4e48\u5b9e\u73b0\u7684&#xff1a;\u4ece\u64cd\u4f5c\u7cfb\u7edf\u5185\u6838\u5230 HotSpot \u5bf9\u8c61\u76d1\u89c6\u5668<br \/>\nThe companion article, From PV Operations and Monitors to Modern Java Concurrency, covers what a monitor is: private data, procedures, implicit mutual exclusion, condition variables, and th<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[99,81,190],"topic":[],"class_list":["post-117804","post","type-post","status-publish","format-standard","hentry","category-server","tag-java","tag-python","tag-190"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/117804.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"\u4e2d\u6587\u7248&#xff1a;\u7ba1\u7a0b\u662f\u600e\u4e48\u5b9e\u73b0\u7684&#xff1a;\u4ece\u64cd\u4f5c\u7cfb\u7edf\u5185\u6838\u5230 HotSpot \u5bf9\u8c61\u76d1\u89c6\u5668 The companion article, From PV Operations and Monitors to Modern Java Concurrency, covers what a monitor is: private data, procedures, implicit mutual exclusion, condition variables, and th\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/117804.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-25T05:16:25+00:00\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"29 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/117804.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/117804.html\",\"name\":\"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-10-25T05:16:25+00:00\",\"dateModified\":\"2026-10-25T05:16:25+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/117804.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/117804.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/117804.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/117804.html","og_locale":"zh_CN","og_type":"article","og_title":"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"\u4e2d\u6587\u7248&#xff1a;\u7ba1\u7a0b\u662f\u600e\u4e48\u5b9e\u73b0\u7684&#xff1a;\u4ece\u64cd\u4f5c\u7cfb\u7edf\u5185\u6838\u5230 HotSpot \u5bf9\u8c61\u76d1\u89c6\u5668 The companion article, From PV Operations and Monitors to Modern Java Concurrency, covers what a monitor is: private data, procedures, implicit mutual exclusion, condition variables, and th","og_url":"https:\/\/www.wsisp.com\/helps\/117804.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-10-25T05:16:25+00:00","author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"29 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/117804.html","url":"https:\/\/www.wsisp.com\/helps\/117804.html","name":"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-10-25T05:16:25+00:00","dateModified":"2026-10-25T05:16:25+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/117804.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/117804.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/117804.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"How Monitors Are Implemented: From the OS Kernel to the HotSpot ObjectMonitor"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/117804","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=117804"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/117804\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=117804"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=117804"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=117804"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=117804"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}