面試官超級(jí)喜歡問(wèn)的MarkWord
前言
年底了,最近好幾天沒(méi)吃飯了,在微博吃瓜吃的飽飽的。
續(xù)上次被問(wèn)到synchronized鎖后,面試官繼續(xù)刁難阿巴阿巴,進(jìn)而深入到對(duì)象頭中相關(guān)的概念。
當(dāng)場(chǎng)拿offer
面試官: 上次提到了synchronized鎖,那你知道synchronized鎖具體是怎么實(shí)現(xiàn)的嗎?
阿巴阿巴: 在JDK版本1.5及之前的版本synchronized主要靠的是Monitor對(duì)象來(lái)完成,同步代碼塊使用的是monitorenter和monitorexit指令,而synchronized修飾方法靠的是ACC_SYNCHRONIZED標(biāo)識(shí),這些都是進(jìn)入到內(nèi)核態(tài)進(jìn)行加鎖的,然后將競(jìng)爭(zhēng)鎖失敗的線程直接掛起,等待后面恢復(fù)。
阿巴阿巴: 在JDK1.6及之后的版本中,synchronized鎖得到了優(yōu)化,引入了自適應(yīng)自旋鎖、偏向鎖、輕量鎖,他們主要優(yōu)化了鎖在一定條件下的性能。避免了一上來(lái)就加重量級(jí)鎖,等待鎖的其他線程只能乖乖掛起,對(duì)cpu性能影響特別大。
阿巴阿巴: 在hotspot虛擬機(jī)中,對(duì)象頭主要包括兩部分 MarkWord和Klass Pointer。
MarkWord 對(duì)象標(biāo)記字段,默認(rèn)存儲(chǔ)的是對(duì)象的HashCode,GC的分代年齡(2bit最大表示15)和鎖的標(biāo)志信息等。對(duì)于32位的虛擬機(jī)MarkWord占32bit,對(duì)于64位的虛擬機(jī)MarkWord占用64字節(jié)。
Klass Pointer Class 對(duì)象的類型指針,它指向?qū)ο髮?duì)應(yīng)的Class對(duì)象的內(nèi)存地址。大小占4字節(jié)(指針壓縮的情況下為4字節(jié),未進(jìn)行指針壓縮則占8字節(jié))。32位虛擬機(jī)MarkWord分布
64位虛擬機(jī)MarkWord分布
圖片來(lái)源https://blog.csdn.net/weixin_40816843/article/details/120811181
查看虛擬機(jī)是多少位的可以使用:java -version
面試官: 我們?cè)趺纯磳?duì)象頭里的MarkWord數(shù)據(jù)呢?
阿巴阿巴: 可以看到在openJDK中關(guān)于MarkWord的描述,首先可以在Github上找到Open Jdk的源碼
gitHub地址:https://github.com/openjdk/jdk
在IDE中打開(kāi)并找到如下的位置
src/hotspot/share/oops/markWord.hpp
- // 查看虛擬機(jī)是多少位的可以使用:java -version
 - // 32 bits:
 - // --------
 - // hash:25 ------------>| age:4 unused_gap:1 lock:2 (normal object)
 - //
 - // 64 bits:
 - // --------
 - // unused:25 hash:31 -->| unused_gap:1 age:4 unused_gap:1 lock:2 (normal object)
 
阿巴阿巴: 當(dāng)然可以引入openjdk提供的jol-core,然后進(jìn)行打印即可。
- // 在pom中引入
 - <dependency>
 - <groupId>org.openjdk.jol</groupId>
 - <artifactId>jol-core</artifactId>
 - <version>0.10</version>
 - </dependency>
 
然后編寫(xiě)如下代碼
- public static void main(String[] args) {
 - Test t = new Test();
 - System.out.println(ClassLayout.parseInstance(t).toPrintable());
 - }
 
打印如下
markword在哪?Klass pointer在哪兒?
1處是MarkWord占用8Byte也就是64bit
2處是Klass Pointer占用了4Byte也就是32bit
klass pointer看起來(lái)是被壓縮了,怎么確定是被壓縮了呢?可以通過(guò)如下命令
面試官: 對(duì)于JDK1.6及以上版本,synchronized和MarkWord有啥關(guān)系嘛?
阿巴阿巴: 那關(guān)系可大了,可以看到在MarkWord中有2bit用來(lái)表示鎖的標(biāo)志位,代表著經(jīng)過(guò)優(yōu)化的synchronized鎖不會(huì)直接上重量級(jí)鎖,而是由偏向鎖轉(zhuǎn)為輕量鎖,再由輕量鎖轉(zhuǎn)為重量級(jí)鎖,一步一步膨脹的過(guò)程。
下面是2bit的鎖標(biāo)志位代表的含義
- // [ptr | 00] locked ptr points to real header on stack
 - // [header | 01] unlocked regular object header
 - // [ptr | 10] monitor inflated lock (header is wapped out)
 - // [ptr | 11] marked used to mark an object
 - // [0 ............ 0| 00] inflating inflation in progress
 - 001 無(wú)鎖狀態(tài) (第一位代表偏向標(biāo)志,為0的時(shí)候表示不偏向,為1的時(shí)候表示偏向)
 - 101 偏向鎖 且記錄線程ID
 - 00 輕量鎖 指向棧中鎖記錄的指針
 - 10 重量級(jí)鎖 重量級(jí)鎖的指針
 - 11 GC標(biāo)志
 
然后再找到上圖Value部分的數(shù)據(jù),這兩位是鎖的標(biāo)志位
面試官: 你剛不是說(shuō)有一位是鎖的偏向標(biāo)志嗎?在哪兒呢?
阿巴阿巴: 鎖的偏向標(biāo)志就在鎖標(biāo)志的前一位
阿巴阿巴: 程序啟動(dòng)后4s就會(huì)加偏向鎖,只不過(guò)這個(gè)偏向鎖沒(méi)有偏向任何線程ID,也屬于無(wú)鎖狀態(tài)
阿巴阿巴: 當(dāng)應(yīng)用處于單線程環(huán)境中時(shí),這時(shí)候上的是偏向鎖,在對(duì)象頭中偏向標(biāo)示顯示為1,案例如下
- public static void main(String[] args) {
 - Test t = new Test();
 - new Thread(()->{
 - synchronized (t) {
 - System.out.println(ClassLayout.parseInstance(t).toPrintable());
 - }
 - }).start();
 - }
 
打印出來(lái)的數(shù)據(jù)如下
阿巴阿巴: 讓程序處于2個(gè)線程交替進(jìn)行競(jìng)爭(zhēng)鎖
- public static void main(String[] args) throws InterruptedException {
 - Test t = new Test();
 - Thread thread = new Thread(()->{
 - synchronized (t) {
 - System.out.println(ClassLayout.parseInstance(t).toPrintable());
 - }
 - });
 - thread.start();
 - // 等待thread運(yùn)行完
 - thread.join();
 - synchronized (t) {
 - System.out.println(ClassLayout.parseInstance(t).toPrintable());
 - }
 - }
 
可以看到當(dāng)main線程拿鎖時(shí)已經(jīng)膨脹為輕量鎖了,鎖的2bit標(biāo)志為變成00了
阿巴阿巴: 輕量鎖的時(shí)候,虛擬機(jī)會(huì)在當(dāng)前線程的棧幀中建立一個(gè)鎖記錄的空間“Lock Record”,用于存儲(chǔ)鎖對(duì)象目前的MarkWord的拷貝,這一步采用CAS,如果成功了,那么與此同時(shí),2bit的鎖標(biāo)記位會(huì)從“01”轉(zhuǎn)變?yōu)?ldquo;00”。這就是加輕量鎖的過(guò)程。
阿巴阿巴: 之所以引入偏向鎖,是為了解決在無(wú)多線程競(jìng)爭(zhēng)環(huán)境下的輕量鎖,輕量鎖CAS多次的嘗試也是對(duì)性能的損耗。相對(duì)于輕量鎖而言,偏向鎖值只需要進(jìn)行一次CAS,這次CAS是用來(lái)設(shè)置線程ID的,設(shè)置成功后就代表獲取鎖了。輕量鎖更適合于線程交替執(zhí)行的場(chǎng)景,它們通過(guò)CAS自旋,避免了線程直接掛起以及掛起后的恢復(fù)過(guò)程,以此來(lái)降低CPU的損耗。
阿巴阿巴: 最后讓我們看看加上重量鎖后的MarkWord表現(xiàn)吧,先上代碼
- public static void main(String[] args) throws InterruptedException {
 - Test t = new Test();
 - Thread thread = new Thread(()->{
 - synchronized (t) {
 - System.out.println(ClassLayout.parseInstance(t).toPrintable());
 - }
 - });
 - thread.start();
 - // 等待thread運(yùn)行完
 - // thread.join(); 去掉該代碼
 - synchronized (t) {
 - System.out.println(ClassLayout.parseInstance(t).toPrintable());
 - }
 - }
 
控制臺(tái)打印如下,發(fā)現(xiàn)已經(jīng)加上重量鎖了,鎖的2bit標(biāo)志為變成10了。
阿巴阿巴: 當(dāng)輕量級(jí)鎖升級(jí)成重量級(jí)鎖時(shí),Mark Word的鎖標(biāo)記位更新為10,Mark Word 將指向互斥量(重量級(jí)鎖)。
阿巴阿巴: 以上就是關(guān)于synchronized和MarkWord的關(guān)系啦。
面試官: 理解的不錯(cuò),明天來(lái)上班吧~
阿巴阿巴: 好的~


























 
 
 











 
 
 
 