面試官超級(jí)喜歡問的MarkWord
前言
年底了,最近好幾天沒吃飯了,在微博吃瓜吃的飽飽的。
續(xù)上次被問到synchronized鎖后,面試官繼續(xù)刁難阿巴阿巴,進(jìn)而深入到對(duì)象頭中相關(guān)的概念。
當(dāng)場(chǎng)拿offer
面試官: 上次提到了synchronized鎖,那你知道synchronized鎖具體是怎么實(shí)現(xiàn)的嗎?
阿巴阿巴: 在JDK版本1.5及之前的版本synchronized主要靠的是Monitor對(duì)象來完成,同步代碼塊使用的是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)化了鎖在一定條件下的性能。避免了一上來就加重量級(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分布
圖片來源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中打開并找到如下的位置
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>
然后編寫如下代碼
- 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看起來是被壓縮了,怎么確定是被壓縮了呢?可以通過如下命令
面試官: 對(duì)于JDK1.6及以上版本,synchronized和MarkWord有啥關(guān)系嘛?
阿巴阿巴: 那關(guān)系可大了,可以看到在MarkWord中有2bit用來表示鎖的標(biāo)志位,代表著經(jīng)過優(yōu)化的synchronized鎖不會(huì)直接上重量級(jí)鎖,而是由偏向鎖轉(zhuǎn)為輕量鎖,再由輕量鎖轉(zhuǎn)為重量級(jí)鎖,一步一步膨脹的過程。
下面是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 無鎖狀態(tài) (第一位代表偏向標(biāo)志,為0的時(shí)候表示不偏向,為1的時(shí)候表示偏向)
- 101 偏向鎖 且記錄線程ID
- 00 輕量鎖 指向棧中鎖記錄的指針
- 10 重量級(jí)鎖 重量級(jí)鎖的指針
- 11 GC標(biāo)志
然后再找到上圖Value部分的數(shù)據(jù),這兩位是鎖的標(biāo)志位
面試官: 你剛不是說有一位是鎖的偏向標(biāo)志嗎?在哪兒呢?
阿巴阿巴: 鎖的偏向標(biāo)志就在鎖標(biāo)志的前一位
阿巴阿巴: 程序啟動(dòng)后4s就會(huì)加偏向鎖,只不過這個(gè)偏向鎖沒有偏向任何線程ID,也屬于無鎖狀態(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();
- }
打印出來的數(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”。這就是加輕量鎖的過程。
阿巴阿巴: 之所以引入偏向鎖,是為了解決在無多線程競(jìng)爭(zhēng)環(huán)境下的輕量鎖,輕量鎖CAS多次的嘗試也是對(duì)性能的損耗。相對(duì)于輕量鎖而言,偏向鎖值只需要進(jìn)行一次CAS,這次CAS是用來設(shè)置線程ID的,設(shè)置成功后就代表獲取鎖了。輕量鎖更適合于線程交替執(zhí)行的場(chǎng)景,它們通過CAS自旋,避免了線程直接掛起以及掛起后的恢復(fù)過程,以此來降低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ò),明天來上班吧~
阿巴阿巴: 好的~