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