大家好,我是慶哥Java,一個專注於乾貨分享的Java自學者!

寫在前面

如果你已經知道什麼是Mark Word,那我也希望你都好好閱讀下本篇文章,因為你有可能發現不一樣的切入點來幫助你更加深入的瞭解Mark Word,這對你來說是個很好的鞏固所學知識的機會,同時也是一場技術交流,一個有逼格的程式設計師應該不會錯過這樣的機會吧!

如果你還不知道什麼是Mark Word,那你更要好好閱讀本篇文章了,因為Mark Word不僅是一個可以讓你用來裝X的詞彙,實際上它是一個非常重要的概念,非常重要的知識點,對你學習Java中的各種鎖是非常有必要的,也可以說是必須的,而為什麼要閱讀本篇文章嘞?

因為,慶哥的文章,接地氣啊,通俗易懂,那咱就一起搞起吧!


先來看下Mark Word

Mark Word是啥?翻譯過來就是物件標記,先通過程式碼讓你直觀看下Mark Word到底是個什麼東東,來看下面程式碼:

class OneClass{

}

這是啥?一個非常非常簡單的類,啥也沒有,就是聲明瞭一個類對吧,好,看接下來的操作:

public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new OneClass()).toPrintable());
}

這是啥?先來看看打印出來的是些個啥?



看得懂嗎?紅框中的就是物件標記Mark Word了,那今天這篇文章的目的就是讓你讀懂上面這張圖到底是個啥?以及有哪些重要的資訊!

事先宣告,今天的文章,乾貨比較多,看起來有點費勁,請先做好準備!

開幹!


從物件例項開始講起

什麼是物件例項呢?說的簡單點,我們new出來的東西就是一個物件例項,也就是平常說的什麼例項化,就是你創建出來的那個在堆裡面的例項物件,ok,這個概念相信大家都懂,不贅述,接下來你就有可能不知道了,你說對於一個物件例項,它有哪幾部分組成呢?

  1. 物件頭
  2. 例項資料
  3. 對齊填充

這些都是啥?這裡我們可以類比下我們的人,一個物件例項就好比是一個完整的人,拿你自己來類比就行,這裡我們拿你自己來類比,這個物件頭就是你的大腦啦,然後例項資料就好比的身體,而對齊填充就好比你的腳,畫個圖大概就是這樣的:



這裡可能這個對齊填充大家不是很好理解,什麼意思呢?就比如說你要參加一個面試,但是人家硬性要求一米八,但是你就是一米七八,咋辦,鞋墊子拯救你啊,懂我意思吧。 那這個對齊填充也是這麼回事,對齊填充要求一個物件例項的大小必須是8個位元組的倍數,那你不夠了的話,對齊填充起作用給你整到8位元組的倍數,懂了吧!

現在我們知道了,一個物件例項包括物件頭,例項資料和對齊填充,其實更詳細的還有如下劃分,看圖:



什麼意思呢?就是你得知道這麼一回事,對於物件頭來說,它是分為兩部分的,一是物件標記,也就是今天要注重說的Mark Word,還有一個就是Class Pointer型別指標了,咱們的重點是Mark Word!

例項資料和對齊填充

這裡先給大家簡單說說什麼是例項資料,幫助大家有個直觀的認識!

什麼叫做例項資料呢?寫一個簡單的程式碼來舉例說明:



我們這裡定義了一個超級簡單的Person類,裡面啥也沒有,這個時候我們去例項化一個Person物件,也就是這樣:

這個時候你知道我們這個物件有多大嗎?實際上目前它就佔16個位元組,也就是物件頭中的Mark Word和型別指標,說白了就是,你剛開始建立一個空蕩蕩的Java物件,起初裡面是隻有物件頭的,也就是組成物件頭的物件標記Mark Word佔8個位元組,型別指標佔8個位元組(存在指標壓縮的話就只有4個位元組,會對齊填充4個位元組),一共16個位元組,如果這個時候我們再新增一點程式碼,比如這樣:

我們給Person類增加了一個屬性,我們知道int佔四個位元組,加上物件頭的16個位元組,此時一共20個位元組,但是一個物件的大小要是8位元組的倍數,那目前20位元組,所以需要到24位元組才符合條件,此時就需要對齊填充4個位元組,最終湊成24個位元組,這個Person類中的一些東西(比如這裡定義的int)就是例項資料了,另外想必這下你也更清楚什麼是對齊填充了吧!

ok,到了這裡,什麼是例項資料,什麼是對齊填充,是不是比較清晰了?當然如果你目前理解的還不是很透徹也沒關係,只有一個大致知道是怎麼回事就ok啦!

型別指標是什麼

這是個啥意思呢?我們來看,比如我們建立一個Person物件,我們一般怎麼寫,是不是這樣:

Person p = new Person()

別看這一行簡單的程式碼,你得分清楚幾個概念了,首先就是new person(),這個大家最熟悉,就是建立一個新的物件,在哪,是在堆中建立吧,然後就是這個p了,它是啥,這個是物件引用,儲存在哪,知道吧,這個是在棧記憶體,那麼這個Person呢?是不是很多人就不太清楚了呢?

記住了,這個Person可以理解成一個模板,就好比人類,然後後面new Person()生產出具體的某個人,而這個Person就是個生產出這個人的模具,最重要的要知道,這個Person存在哪裡,它是在方法區的,結合上面說的物件標記,來看一張圖:



一定要注意這個Person的位置在哪裡!它是存在方法區的,這個是java虛擬機器的知識了,先作簡單瞭解即可!

而這裡的型別指標,就是指向這個Person類的指標了!那麼關於什麼是型別指標,你是否也有一個簡單的認識了?不再是一個陌生的詞彙了吧!

這就ok啦,咱們繼續,重難點還在後面嘞!


詳解Mark Word

對於一個Java物件來說,物件頭是極其重要的,物件頭主要有物件標記和型別指標兩部分組成,接下來需要重點看下物件標記,也就是Mark Word,下面是針對64位JVM(那也就是說還有32位的,考慮到現在基本都是64的了,32的直接pass掉)的Mark Word來說的,它的組成是這樣的:



第一次看到這張圖會覺得比較懵,沒事,很正常,我剛開始學習也是這樣,彆著急,經過下面的一步步的分析,你就會清晰很多!首先記住,上圖表示的就是一個Mark Word的內部是啥樣的!我們之前不是說了嘛,物件標記Mark Word是佔8位元組的,那也就是64bit,那這64bit都是儲存的啥,上面這種圖就給出了答案,你看這裡:



一個Mark Word就是8位元組64位,裡面儲存了啥,就得看上面這張圖,有內味兒了吧,上面這張圖是不是稍微熟悉了一點點,那現在你應該知道這麼些東西:

一個Java物件由物件頭,例項資料和對齊填充組成,其中物件頭是極其重要的,物件頭是由物件標記Mark Word和型別指標組成,其中又以Mark Word最重要,對於Mark Word它佔8個位元組也就是64位!

ok,以上內容都清楚吧!那咱就繼續!

接下來咱就以鎖這個切入點去詳細的看看這個Mark Word!

我們都知道在併發情況下也就是要解決執行緒安全的話要加鎖,其實這個加鎖就是對物件加鎖,那如何去判斷或者說知道這個物件加鎖了沒有或者加的什麼鎖,這些個資訊其實就儲存在物件頭中的Mark Word中,上面這張圖就是關於一個Mark Word的具體結構,可以看出,一個Java物件其實在不同的狀態下,它是不一樣的,主要是有不同的鎖,比如沒有鎖,輕量級鎖,還有什麼偏向鎖,重量級鎖等等,不同的鎖,在Mark Word中就會有不同的狀態標記。

JOL的引入

只看這些,其實是比較晦澀難懂的,接下來需要結合程式碼來看下:



這就是一個空類,啥也沒有,要怎麼看這個物件標記Mark Word呢?這裡我們需要加入一個依賴,也就是你的Java專案是個maven專案,可以引入pom依賴,然後新增以下依賴:

<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>

這個依賴是幹嘛的呢?這是一個程式碼工具叫做JOL,也就是Java Object Layout,主要就是用來分析java虛擬機器中的物件佈局的,也就是在java物件在虛擬機器中的大小和分佈,ok,接下來加入上述這個依賴之後我們就可以這樣操作,看程式碼:

這樣我們就可以看出使用虛擬機器的一些情況,看列印輸出:



接著我們就來看下我們建立的那個空類是怎樣的一個情況,可以這樣操作:

MyClass myClass = new MyClass();
//打印出相關的物件頭資訊
System.out.println(ClassLayout.parseInstance(myClass).toPrintable());

得出以下內容:

OK了,相關資訊打印出來了,記住這是myclass的相關內部資訊,那這些都是啥呢?看上面的圖,是不是有個“Object header”啥意思?不就是物件頭嘛,然後我們來看這裡:

這是啥?主要就是來看我們的一個空物件佔多大空間,之前也說了,物件頭包括物件標記8位元組和型別指標8位元組,我們先來看物件標記,是不是這個:



也就是起始位置是0,然後前進4個位元組,此時的起始位置就變成了4,接著再前進4個位元組,那此時起始位置就成了8,此時物件標記就佔8個位元組,這沒啥問題,接著我們看,起始位置是8,然後繼續前進4位元組,到達12的時候,其實此時型別指標就結束了,也就是8到12,型別指標就佔了4位元組,看這句話:

也就是此時這個物件就佔據12個位元組,可是不是說佔據16個位元組才對嗎?我們知道java物件所佔大小需要是8位元組的整數倍,那為了滿足這塊,就又對齊填充了4位元組,最終達到16位元組 。

為什麼會這樣?其實是因為這裡發生了指標壓縮,在說這個指標壓縮之前,需要明白這樣的一東西:

我們通過程式碼來說下:

我們在空類中加入一個char型別,再看下結果:

接著我們再看一個:



我就問此時你說會進行對齊填充幾個位元組?思考一下:

為什麼是6,一定要想明白了,記住8的倍數哦!對了,一個物件有多大,記得可以看這裡:



指標壓縮

接著我們再說之前指標壓縮的問題,也就是型別指標是佔8個位元組的,可是實際上只佔了4個位元組,這就是指標壓縮的問題,這裡我們需要這麼一個jvm引數,就是它:

java -XX:+PrintCommandLineFlags -version

我們執行看下:



也就是預設開啟了指標壓縮,所以我們的型別指標才變成了4位元組,接著我們可以把這個指標壓縮給關閉掉,可以這樣操作:

再看:

此時就正確啦,就是物件標記佔8個位元組,型別指標也佔8個位元組,也就是物件頭16個位元組。

我們之前說過,一個java物件包括物件頭,例項資料和對齊填充,經過我們上面的講解,然後在看下面一張圖,我相信你可以秒懂:



有沒有一種豁然開朗的感覺?到了這裡就需要分析最後一波,就是value值了,也就是這部分值(注意我們重點在Mark Word的value值上):

value值分析

那這裡需要再看看一下這張非常重要的圖了:



然後再來看我們這裡建立的類:



這就是一個普通的類,沒有加鎖,也就是最普通的無鎖,我們再看它的物件結構是啥:

我們主要來看value這部分,這裡我們仍然需要對照Mark Word的結構來看,這裡是無鎖結構,所以我們需要看的是這裡:



注意重點等下我們需要看下這個鎖標誌位,然後下面看一個對照圖:

一張圖理解Mark Word



要仔細看這張圖了,懂了這個,基本上你就掌握了Mark Word!為了讓你更加的清晰,再給你來一張圖:



總結

對於一個Java物件而言,Mark Word是相當重要的,而且它是你後續學習併發程式設計,研究Java中的各種鎖需要必會的知識,剛開始可能覺得是個新奇陌生的詞彙,但是等你慢慢的去接觸它。瞭解它,你會發現它真的能幫你解決很多看起來很難的知識,會讓你覺得那些看起來很高大上的東西,原來是這麼回事啊,比如偏向鎖,輕量級鎖什麼的,總之,作為一個Java程式設計師,Mark Word是你需要要掌握的!

當然,由於個人水平有限,本篇描述可能存在有誤的地方,煩請指正,大家共同學習,一起進步!