JVM三部分:
1.類加載子系統(tǒng)
2.內(nèi)存空間(運行時數(shù)據(jù)空間)
3.執(zhí)行引擎
JVM運行時數(shù)據(jù)區(qū)
XXX.java ---->編譯后:XXX.class ---->類裝載子系統(tǒng)--->裝載至JVM運行時數(shù)據(jù)區(qū)(內(nèi)存)--->執(zhí)行引擎執(zhí)行
由所有線程共享的數(shù)據(jù)區(qū):方法區(qū)油猫,堆
線程隔離數(shù)據(jù)區(qū):java棧夕凝,本地方法棧铃绒,程序計數(shù)器
數(shù)據(jù)結(jié)構(gòu):https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
堆鸽照,垃圾回收GC方式 針對full GC(zgc,G1颠悬,cms)
內(nèi)存可見性矮燎、重排序、順序一致性、volatile腻扇、鎖障陶、final
Valatile 實現(xiàn)及應(yīng)用
volatile關(guān)鍵字,在轉(zhuǎn)為匯編語言后浅乔,會發(fā)現(xiàn)有l(wèi)ock前綴的指令
0x0000000002931351: lock add dword ptr [rsp],0h ;*putstatic instance
; - org.xrq.test.design.singleton.LazySingleton::getInstance@13 (line 14)
lock前綴指令會強制處理器將修改內(nèi)容回寫到內(nèi)存,同時铝条,通過緩存一致性協(xié)議(MESI)來控制緩存一致
即靖苇,一個處理器的緩存回寫到內(nèi)存會導(dǎo)致其他處理器的緩存無效
MESI: modified(修改),exclusive(獨占)班缰,share(共享)贤壁,Invalid(無效)
具體步驟及結(jié)果參考:http://www.cnblogs.com/xrq730/p/7048693.html
synchronized 實現(xiàn)及原理
Java中每一個對象都可以作為鎖,這是synchronized實現(xiàn)同步的基礎(chǔ):
1. 普通同步方法埠忘,鎖是當(dāng)前實例對象
2. 靜態(tài)同步方法脾拆,鎖是當(dāng)前類的class對象
3. 同步方法塊,鎖是括號里面的對象
Java 虛擬機中的同步(Synchronization)基于進入和退出管程(Monitor)對象實現(xiàn)莹妒, 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此名船。在 Java 語言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法旨怠。同步方法 并不是由 monitorenter 和 monitorexit 指令來實現(xiàn)同步的渠驼,而是由方法調(diào)用指令讀取運行時常量池中方法的 ACC_SYNCHRONIZED 標(biāo)志來隱式實現(xiàn)的
如下
public class SynchronizedTest {
public synchronized void test1(){
}
public void test2(){
synchronized (this){
}
}
}
利用javap工具查看生成的class文件信息來分析Synchronize的實現(xiàn)
Java對象頭,這對深入理解synchronized實現(xiàn)原理非常關(guān)鍵鉴腻。
所有的Java對象是天生的Monitor迷扇,每一個Java對象都有成為Monitor的潛質(zhì),因為在Java的設(shè)計中 爽哎,每一個Java對象自打娘胎里出來就帶了一把看不見的鎖蜓席,它叫做內(nèi)部鎖或者Monitor鎖。
理解Java對象頭與Monitor
本段摘自:https://blog.csdn.net/javazejian/article/details/72828483
在JVM中课锌,對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭厨内、實例數(shù)據(jù)和對齊填充。如下:
實例變量:存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息隘庄,如果是數(shù)組的實例部分還包括數(shù)組的長度踢步,這部分內(nèi)存按4字節(jié)對齊。
填充數(shù)據(jù):由于虛擬機要求對象起始地址必須是8字節(jié)的整數(shù)倍丑掺。填充數(shù)據(jù)不是必須存在的获印,僅僅是為了字節(jié)對齊,這點了解即可街州。
而對于頂部兼丰,則是Java頭對象,它實現(xiàn)synchronized的鎖對象的基礎(chǔ)唆缴,這點我們重點分析它鳍征,一般而言,synchronized使用的鎖對象是存儲在Java對象頭里的面徽,jvm中采用2個字來存儲對象頭(如果對象是數(shù)組則會分配3個字艳丛,多出來的1個字記錄的是數(shù)組長度),其主要結(jié)構(gòu)是由Mark Word 和 Class Metadata Address 組成趟紊,其結(jié)構(gòu)說明如下表:
虛擬機位數(shù) | 頭對象結(jié)構(gòu) | 說明 |
---|---|---|
32/64bit | Mark Word | 存儲對象的hashCode氮双、鎖信息或分代年齡或GC標(biāo)志等信息 |
32/64bit | Class Metadata Address | 類型指針指向?qū)ο蟮念愒獢?shù)據(jù),JVM通過這個指針確定該對象是哪個類的實例霎匈。 |
其中Mark Word在默認情況下存儲著對象的HashCode戴差、分代年齡、鎖標(biāo)記位等以下是32位JVM的Mark Word默認存儲結(jié)構(gòu)
鎖狀態(tài) | 25bit | 4bit | 1bit是否是偏向鎖 | 2bit 鎖標(biāo)志位 |
---|---|---|---|---|
無鎖狀態(tài) | 對象HashCode | 對象分代年齡 | 0 | 01 |
由于對象頭的信息是與對象自身定義的數(shù)據(jù)沒有關(guān)系的額外存儲成本铛嘱,因此考慮到JVM的空間效率暖释,Mark Word 被設(shè)計成為一個非固定的數(shù)據(jù)結(jié)構(gòu),以便存儲更多有效的數(shù)據(jù)墨吓,它會根據(jù)對象本身的狀態(tài)復(fù)用自己的存儲空間球匕,如32位JVM下,除了上述列出的Mark Word默認存儲結(jié)構(gòu)外帖烘,還有如下可能變化的結(jié)構(gòu):
其中輕量級鎖和偏向鎖是Java 6 對 synchronized 鎖進行優(yōu)化后新增加的谐丢,稍后我們會簡要分析。這里我們主要分析一下重量級鎖也就是通常說synchronized的對象鎖蚓让,鎖標(biāo)識位為10,其中指針指向的是monitor對象(也稱為管程或監(jiān)視器鎖)的起始地址讥珍。每個對象都存在著一個 monitor 與之關(guān)聯(lián)历极,對象與其 monitor 之間的關(guān)系有存在多種實現(xiàn)方式,如monitor可以與對象一起創(chuàng)建銷毀或當(dāng)線程試圖獲取對象鎖時自動生成衷佃,但當(dāng)一個 monitor 被某個線程持有后趟卸,它便處于鎖定狀態(tài)。在Java虛擬機(HotSpot)中,monitor是由ObjectMonitor實現(xiàn)的锄列,其主要數(shù)據(jù)結(jié)構(gòu)如下(位于HotSpot虛擬機源碼ObjectMonitor.hpp文件图云,C++實現(xiàn)的)
ObjectMonitor() {
_header = NULL;
_count = 0; //記錄個數(shù)
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //處于wait狀態(tài)的線程,會被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程邻邮,會被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中有兩個隊列竣况,_WaitSet 和 _EntryList,用來保存ObjectWaiter對象列表( 每個等待鎖的線程都會被封裝成ObjectWaiter對象)筒严,_owner指向持有ObjectMonitor對象的線程丹泉,當(dāng)多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合鸭蛙,當(dāng)線程獲取到對象的monitor 后進入 _Owner 區(qū)域并把monitor中的owner變量設(shè)置為當(dāng)前線程同時monitor中的計數(shù)器count加1摹恨,若線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的monitor娶视,owner變量恢復(fù)為null晒哄,count自減1,同時該線程進入 WaitSe t集合中等待被喚醒肪获。若當(dāng)前線程執(zhí)行完畢也將釋放monitor(鎖)并復(fù)位變量的值寝凌,以便其他線程進入獲取monitor(鎖)。如下圖所示
由此看來贪磺,monitor對象存在于每個Java對象的對象頭中(存儲的指針的指向)硫兰,synchronized鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對象可以作為鎖的原因
當(dāng)一個線程訪問同步代碼塊時寒锚,它首先是需要得到鎖才能執(zhí)行同步代碼劫映,當(dāng)退出或者拋出異常時必須要釋放鎖
Java虛擬機對synchronized的優(yōu)化
鎖的四種狀態(tài):
無鎖狀態(tài)
偏向鎖狀態(tài):只有一個線程進入臨界區(qū)(加鎖解鎖不需要額外消耗,直接比較對象頭里當(dāng)前持有的線程ID刹前,但是如果存在鎖競爭會有額外的所撤銷消耗)
輕量級鎖狀態(tài):多個線程先后(交替)進入臨界區(qū)(線程競爭使用自旋泳赋,不會阻塞,但是始終得不到鎖會消耗CPU)
重量級鎖狀態(tài):多個線程同時進入臨界區(qū)(線程競爭不使用自旋喇喉,不會消耗CPU祖今,但是線程阻塞,相應(yīng)時間緩慢)
鎖升級觸發(fā)條件:
處于偏向鎖狀態(tài)時拣技,有其他線程發(fā)起鎖競爭千诬,原持有鎖線程未退出同步代碼塊——>升級為輕量級鎖狀態(tài)
處于輕量級鎖狀態(tài)時,其他線程多次自旋膏斤,cas操作仍然失敗徐绑,未獲取到鎖——>升級為重量級鎖狀態(tài)
處于重量級鎖狀態(tài)時,線程會阻塞莫辨,等待原持有鎖線程完成后退出再被喚醒
參考:
https://www.zhihu.com/question/53826114
https://blog.csdn.net/javazejian/article/details/72828483