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