Monitor系列問(wèn)題問(wèn)答
1、簡(jiǎn)介
2、對(duì)象頭
3、Mark Word
4、Monitor
5、monitorente && monitorexit
1、簡(jiǎn)介
我們Java程序員編碼時(shí)談?wù)摰淖疃嗟膬蓚€(gè)字就是對(duì)象,Java中幾乎所有的技術(shù)都是圍繞對(duì)象展開(kāi)。本文將要講述的Monitor并不是Java對(duì)象,而是在操作系統(tǒng)中關(guān)聯(lián)的“對(duì)象”,Monitor是Java重量級(jí)鎖synchronized實(shí)現(xiàn)的關(guān)鍵,因此學(xué)習(xí)Java單機(jī)同步機(jī)制就離不開(kāi)對(duì)Monitor的剖析。Monitor經(jīng)常被人們稱為監(jiān)視器鎖和管程。
2、對(duì)象頭
Monitor與Java對(duì)象頭相關(guān)聯(lián),因此剖析Monitor之前必須了解Java對(duì)象的組成結(jié)構(gòu)。Java對(duì)象在內(nèi)存中由三部分組成,分別是對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充。以32位虛擬機(jī)為例(64位不同),對(duì)象頭(Header)占8個(gè)字節(jié)共64位(數(shù)組對(duì)象頭與普通對(duì)象頭不同,數(shù)組對(duì)象頭12個(gè)字節(jié)共96位);實(shí)例數(shù)據(jù)(Instance Data)存儲(chǔ)這對(duì)象的實(shí)際數(shù)據(jù),因此大小與實(shí)際數(shù)據(jù)大小一致;對(duì)齊填充(Padding)是可選項(xiàng),用于將內(nèi)存對(duì)齊為8字節(jié)的整數(shù)倍。
普通對(duì)象內(nèi)存組成
如上兩張圖展示了Java對(duì)象內(nèi)存結(jié)構(gòu),本文說(shuō)的Monitor和這個(gè)有啥關(guān)系呢?其實(shí)對(duì)象頭(Header)中的Mark Word就是用來(lái)存放Monitor對(duì)象的指針的,在一開(kāi)始小捌就說(shuō)了Monitor并不是Java對(duì)象,而是在操作系統(tǒng)中關(guān)聯(lián)的“對(duì)象”,因此Java對(duì)象如果想要和Monitor進(jìn)行關(guān)聯(lián),就必須在Java對(duì)象中記錄Monitor的內(nèi)存地址,這樣才能通過(guò)Java對(duì)象找到這個(gè)Monitor嘛!
注:Klass Word存放的是指向?qū)ο髮?duì)應(yīng)的Class對(duì)象的指針。
3、Mark Word
可想而知,想要深入探討Monitor肯定避不開(kāi)Mark Word,這個(gè)時(shí)候暴躁的程序員小哥肯定不爽了,你特么不是說(shuō)Mark Word中存放的Monitor的內(nèi)存地址么,我知道了啊……
別急,并不是想的那樣的,這里稍微有一丟丟復(fù)雜,聽(tīng)我慢慢道來(lái)。
Java對(duì)象在不同的狀態(tài)下,Mark Word存儲(chǔ)的值完全不同,尤其是在JDK1.6對(duì)鎖優(yōu)化之后,Mark Word這32bits內(nèi)存空間,真的是被Java大師們壓榨到了極致。了解這個(gè)需要有一定的JVM和synchronized知識(shí),如果不懂的話也無(wú)所謂,先了解就好,后面我們一起學(xué)習(xí)synchronized鎖升級(jí)過(guò)程。
初始狀態(tài)下Java對(duì)象頭的Mark Word里默認(rèn)存儲(chǔ)的是對(duì)象的hashcode、GC分代年齡、是否偏向鎖和鎖標(biāo)志位
4、Monitor
上面鋪墊了這么多東西,其實(shí)就是為了講述Monitor和Java對(duì)象頭中Mark Word的關(guān)系,可以看出來(lái)只有在重量級(jí)鎖的情況下Java對(duì)象頭中Mark Word才會(huì)關(guān)聯(lián)一個(gè)Monitor對(duì)象,那么Monitor又是個(gè)什么東西呢?我相信你一定很好奇吧!
Monitor內(nèi)部分由三部分組成分別是Owner、EntryList、WaitSet;
Owner用于記錄當(dāng)前Monitor的所屬線程
EntryList是一個(gè)鏈表結(jié)構(gòu),用于記錄阻塞在當(dāng)前鎖對(duì)象上的線程
WaitSet用于記錄獲取鎖之后進(jìn)入Waiting狀態(tài)的線程
當(dāng)對(duì)象獲取到鎖之后,由于某些資源并未準(zhǔn)備完成,需要等待其他線程去準(zhǔn)備資源,此時(shí)線程會(huì)通過(guò)wait()/notify()等方法進(jìn)入等待/通知模式,在這種情況下線程釋放鎖之后會(huì)進(jìn)入WaitSet,當(dāng)其他線程準(zhǔn)備好資源之后會(huì)通知WaitSet中等待的線程,WaitSet中的線程會(huì)進(jìn)入到EntryList中,重新參與鎖競(jìng)爭(zhēng)。
————————————————
版權(quán)聲明:本文為CSDN博主「李子捌」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_41125219/article/details/121687007
5、monitorente && monitorexit
知道了Monitor是什么,也知道了Java對(duì)象與Monitor之間的關(guān)系,但是還有一層疑問(wèn);程序在運(yùn)行過(guò)程中是如何知道要給Java對(duì)象去關(guān)聯(lián)一個(gè)Monitor呢?
這就需要一點(diǎn)點(diǎn)Java字節(jié)碼相關(guān)的知識(shí)了,Java的源代碼在編譯器編譯之后生成的Class文件中存儲(chǔ)的是字節(jié)碼指令,程序執(zhí)行本質(zhì)上是一條條指令按照既定順序的流水線工作,那這只有一種可能了,編譯器在編譯成Java字節(jié)碼時(shí)做了記號(hào),這個(gè)記號(hào)就是monitorente /monitorexit。
我們先來(lái)看一段簡(jiǎn)單的synchronized代碼塊:


0 getstatic #2 <com/lzb/concurrency/demo2/MonitorDemo.LOCK : Ljava/lang/Object;> 3 dup 4 astore_1 5 monitorenter 6 getstatic #3 <com/lzb/concurrency/demo2/MonitorDemo.count : I> 9 iconst_1 10 iadd 11 putstatic #3 <com/lzb/concurrency/demo2/MonitorDemo.count : I> 14 aload_1 15 monitorexit 16 goto 24 (+8) 19 astore_2 20 aload_1 21 monitorexit 22 aload_2 23 athrow 24 return
前三行字節(jié)碼分別表示:
- getstatic 獲取靜態(tài)鎖對(duì)象LOCK
- dup 復(fù)制一份LOCK對(duì)象的引用,用于鎖退出
-
astore_1 復(fù)制的引用存入臨時(shí)變量1中 3 dup reference -> slot 1
synchronized臨界區(qū)七行字節(jié)碼分別表示:
monitorenter 關(guān)聯(lián)一個(gè)操作系統(tǒng)Monitor對(duì)象,替換LOCK對(duì)象的Mark Word為Monitor地址
getstatic 獲取靜態(tài)變量count
iadd count++操作
putstatic 賦值++操作后的count
aload_1 獲取LOCK對(duì)象的引用,上面dup復(fù)制后astore_1 指令存儲(chǔ)的那份地址
monitorexit 還原Mark Word,將Monitor對(duì)象指針替換為monitorenter 加鎖時(shí)保存在Monitor對(duì)象中的數(shù)據(jù),如hashcode、分代年齡等數(shù)據(jù);同時(shí)喚醒等待在EntryList中阻塞等待的線程。
異常表中有兩行記錄:
第一行表示:6 -> 16行字節(jié)碼中發(fā)生了異常,會(huì)跳轉(zhuǎn)到19行,這就是synchronized加鎖的代碼區(qū)域,如果加鎖中出現(xiàn)異常,JVM會(huì)處理異常,正確釋放鎖
第二行表示:19 -> 22行字節(jié)碼中發(fā)生了異常,會(huì)跳轉(zhuǎn)到19行,
未發(fā)生異常情況:
goto 24 (+8) 沒(méi)有產(chǎn)生異常,直接執(zhí)行24行指令
return 方法運(yùn)行結(jié)束
發(fā)生異常情況:
astore_2 將異常對(duì)象存儲(chǔ)到臨時(shí)變量中 e -> slot 2
aload_1 加載LOCK鎖對(duì)象引用地址
monitorexit 還原Mark Word,將Monitor對(duì)象指針替換為monitorenter 加鎖時(shí)保存在Monitor對(duì)象中的數(shù)據(jù),如hashcode、分代年齡等數(shù)據(jù);同時(shí)喚醒等待在EntryList中阻塞等待的線程。
aload_2 加載異常對(duì)象
athrow 拋出異常對(duì)象
return 方法運(yùn)行結(jié)束
