<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java 對(duì)象頭信息分析和三種鎖的性能對(duì)比

          共 14507字,需瀏覽 30分鐘

           ·

          2021-09-21 16:59

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進(jìn)!你不來,我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

          編輯:業(yè)余草

          推薦:https://www.xttblog.com/?p=5277

          Java 頭的信息分析

          首先為什么我要去研究 java 的對(duì)象頭呢?這里截取一張 hotspot 的源碼當(dāng)中的注釋。

          Java 頭的信息分析

          這張圖換成可讀的表格如下:

          java 的對(duì)象頭

          意思是 java 的對(duì)象頭在對(duì)象的不同狀態(tài)下會(huì)有不同的表現(xiàn)形式,主要有三種狀態(tài),無鎖狀態(tài)、加鎖狀態(tài)、gc 標(biāo)記狀態(tài)。

          那么我可以理解 java 當(dāng)中的取鎖其實(shí)可以理解是給對(duì)象上鎖,也就是改變對(duì)象頭的狀態(tài),如果上鎖成功則進(jìn)入同步代碼塊。

          但是 java 當(dāng)中的鎖有分為很多種,從上圖可以看出大體分為偏向鎖、輕量鎖、重量鎖三種鎖狀態(tài)。

          這三種鎖的效率 完全不同、關(guān)于效率的分析會(huì)在下文分析,我們只有合理的設(shè)計(jì)代碼,才能合理的利用鎖、那么這三種鎖的原理是什么? 所以我們需要先研究這個(gè)對(duì)象頭。

          java對(duì)象的布局以及對(duì)象頭的布局

          使用 JOL 來分析 java 的對(duì)象布局,添加依賴。

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

          測(cè)試類:

          public class JOLExample1 {
              static  B b = new B();
              public static void main(String [] args) {
                  //jvm的信息
                  out.println(VM.current().details());
                  out.println(ClassLayout.parseInstance(b).toPrintable());
              }
          }

          看下結(jié)果:

          jvm的信息

          分析結(jié)果 1:整個(gè)對(duì)象一共 16B,其中對(duì)象頭(Object header)12B,還有 4B 是對(duì)齊的字節(jié)(因?yàn)樵?64 位虛擬機(jī)上對(duì)象的大小必 須是 8 的倍數(shù))。

          由于這個(gè)對(duì)象里面沒有任何字段,故而對(duì)象的實(shí)例數(shù)據(jù)為 0B?

          兩個(gè)問題:

          1、什么叫做對(duì)象的實(shí)例數(shù)據(jù)呢?

          2、那么對(duì)象頭里面的 12B 到底存的是什么呢?

          首先要明白什么對(duì)象的實(shí)例數(shù)據(jù)很簡(jiǎn)單,我們可以在 B 當(dāng)中添加一個(gè) boolean 的字段,大家都知道 boolean 字段占 1B,然后再看結(jié)果。

          對(duì)象的實(shí)例數(shù)據(jù)

          整個(gè)對(duì)象的大小還是沒有改變一共 16B,其中對(duì)象頭(Object header)12B, boolean 字段 flag(對(duì)象的實(shí)例數(shù)據(jù))占 1B、剩下的 3B 就是對(duì)齊字節(jié)。

          由此我們可以認(rèn)為一個(gè)對(duì)象的布局大體分為三個(gè)部分分別是:對(duì)象頭(Object header)、 對(duì)象的實(shí)例數(shù)據(jù)和字節(jié)對(duì)齊。

          接下來討論第二個(gè)問題,對(duì)象頭為什么是 12B?這個(gè) 12B 當(dāng)中分別存儲(chǔ)的是什么呢?(不同位數(shù)的 VM 對(duì)象頭的長度不一 樣,這里指的是 64bit 的 vm)。

          首先引用 openjdk 文檔當(dāng)中對(duì)對(duì)象頭的解釋:

          對(duì)象頭的解釋

          上述引用中提到一個(gè) java 對(duì)象頭包含了 2 個(gè) word,并且好包含了堆對(duì)象的布局、類型、GC 狀態(tài)、同步狀態(tài)和標(biāo)識(shí)哈希碼,具體怎么包含的呢?又是哪兩個(gè) word呢?

          同步狀態(tài)和標(biāo)識(shí)哈希碼

          mark word 為第一個(gè) word 根據(jù)文檔可以知他里面包含了鎖的信息,hashcode,gc 信息等等,第二個(gè) word 是什么 呢?

          鎖的信息,hashcode,gc 信息

          klass word 為對(duì)象頭的第二個(gè) word 主要指向?qū)ο蟮脑獢?shù)據(jù)。

          元數(shù)據(jù)

          假設(shè)我們理解一個(gè)對(duì)象頭主要上圖兩部分組成(數(shù)組對(duì)象除外,數(shù)組對(duì)象的對(duì)象頭還包含一個(gè)數(shù)組長度)。

          那么 一個(gè) java 的對(duì)象頭多大呢?我們從 JVM 的源碼注釋中得知到一個(gè) mark word 一個(gè)是 64bit,那么 klass 的長度是多少呢?

          所以我們需要想辦法來獲得 java 對(duì)象頭的詳細(xì)信息,驗(yàn)證一下他的大小,驗(yàn)證一下里面包含的信息是否正確。

          根據(jù)上述利用 JOL 打印的對(duì)象頭信息可以知道一個(gè)對(duì)象頭是 12B,其中 8B 是 mark word 那么剩下的 4B 就是 klass word 了,和鎖相關(guān)的就是 mark word 了。

          那么接下來重點(diǎn)分析 mark word 里面信息 在無鎖的情況下 markword 當(dāng)中的前 56bit 存的是對(duì)象的 hashcode,那么來驗(yàn)證一下

          先上代碼:手動(dòng)計(jì)算 HashCode。

          public class HashUtil {
              public static void countHash(Object object) throws NoSuchFieldException, IllegalAccessException {
                  // 手動(dòng)計(jì)算HashCode
                  Field field = Unsafe.class.getDeclaredField("theUnsafe");
                  field.setAccessible(true);
                  Unsafe unsafe = (Unsafe) field.get(null);
                  long hashCode = 0;
                  for (long index = 7; index > 0; index--) {
                      // 取Mark Word中的每一個(gè)Byte進(jìn)行計(jì)算
                      hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index - 1) * 8);
                  }
                  String code = Long.toHexString(hashCode);
                  System.out.println("util-----------0x"+code);
              }
          }
          public class JOLExample2 {
              public static void main(String[] args) throws Exception {
                  B b = new B();
                  out.println("befor hash");
                  //沒有計(jì)算HASHCODE之前的對(duì)象頭
                  out.println(ClassLayout.parseInstance(b).toPrintable());
                  //JVM 計(jì)算的hashcode
                  out.println("jvm------------0x"+Integer.toHexString(b.hashCode()));
                  HashUtil.countHash(b);
                  //當(dāng)計(jì)算完hashcode之后,我們可以查看對(duì)象頭的信息變化
                  out.println("after hash");
                  out.println(ClassLayout.parseInstance(b).toPrintable());

              }
          }
          小端存儲(chǔ)

          分析結(jié)果 3:

          上面沒有進(jìn)行 hashcode 之前的對(duì)象頭信息,可以看到的 56bit 沒有值,打印完 hashcode 之后就有值了,為什么是 1-7B,不是 0-6B 呢?因?yàn)槭?strong style="color: rgb(53, 148, 247);">「小端存儲(chǔ)」。

          其中兩行是我們通過 hashcode 方法打印的結(jié)果,第一行是我根據(jù) 1-7B 的信息計(jì)算出來的 hashcode,所以可以確定 java 對(duì)象頭當(dāng)中的 mark work 里面的后七個(gè)字節(jié)存儲(chǔ)的是 hashcode 信息。

          那么第一個(gè)字節(jié)當(dāng)中的八位分別存的就是分帶年齡、偏向鎖信息,和對(duì)象狀態(tài),這個(gè) 8bit 分別表示的信息如下圖(其實(shí)上圖也有信息),這個(gè)圖會(huì)隨著對(duì)象狀態(tài)改變而改變,下圖是無鎖狀態(tài)下。

          分帶年齡、偏向鎖信息

          關(guān)于對(duì)象狀態(tài)一共分為五種狀態(tài),分別是無鎖、偏向鎖、輕量鎖、重量鎖、GC 標(biāo)記。

          那么 2bit,如何能表示五種狀 態(tài)(2bit 最多只能表示 4 中狀態(tài)分別是:00,01,10,11)。

          jvm 做的比較好的是把偏向鎖和無鎖狀態(tài)表示為同一個(gè)狀態(tài),然 后根據(jù)圖中偏向鎖的標(biāo)識(shí)再去標(biāo)識(shí)是無鎖還是偏向鎖狀態(tài)。

          什么意思呢?寫個(gè)代碼分析一下,「在寫代碼之前我們先記得 無鎖狀態(tài)下的信息00000001」,然后寫一個(gè)偏向鎖的例子看看結(jié)果。

          public static void main(String[] args) throws Exception {
              //-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
              B b = new B();
              out.println("befor lock");
              out.println(ClassLayout.parseInstance(b).toPrintable());
              synchronized (b){
                  out.println("lock ing");
                  out.println(ClassLayout.parseInstance(b).toPrintable());
              }
              out.println("after lock");
              out.println(ClassLayout.parseInstance(b).toPrintable());
          }
          無鎖狀態(tài)

          上面這個(gè)程序只有一個(gè)線程去調(diào)用 sync 方法,故而講道理應(yīng)該是偏向鎖,但是此時(shí)卻是輕量級(jí)鎖。

          而且你會(huì)發(fā)現(xiàn)最后輸出的結(jié)果(第一個(gè)字節(jié))依 然是 00000001 和無鎖的時(shí)候一模一樣,其實(shí)這是因?yàn)樘摂M機(jī)在啟動(dòng)的時(shí)候?qū)τ谄蜴i有延遲。

          比如把上述代碼當(dāng)中加上睡眠 5 秒的代碼,結(jié)果就會(huì)不一樣了。

          public static void main(String[] args) throws Exception {
              //-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
              Thread.sleep(5000);
              B b = new B();
              out.println("befor lock");
              out.println(ClassLayout.parseInstance(b).toPrintable());
              synchronized (b){
                  out.println("lock ing");
                  out.println(ClassLayout.parseInstance(b).toPrintable());
              }
              out.println("after lock");
              out.println(ClassLayout.parseInstance(b).toPrintable());
          }
          偏向鎖有延遲

          結(jié)果變成 00000101。當(dāng)然為了方便測(cè)試我們也可以直接通過 JVM 的參數(shù)來禁用延遲。

          -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
          禁用延遲

          結(jié)果是和睡眠 5 秒一樣的。

          想想為什么偏向鎖會(huì)延遲?因?yàn)閱?dòng)程序的時(shí)候,jvm 會(huì)有很多操作,包括 gc 等等,jvm 剛運(yùn)行時(shí)存在大量的同步方法,很多都不是偏向鎖。

          「而偏向鎖升級(jí)為輕/重量級(jí)鎖的很費(fèi)時(shí)間和資源,因此 jvm 會(huì)延遲 4 秒左右再開啟偏向鎖?!?/strong>

          「那么為什么同步之前就是偏向鎖呢?我猜想是 jvm 的原因,目前還不清楚?!?/strong>

          需要注意的 after lock,退出同步后依然保持了偏向信息。

          然后看下輕量級(jí)鎖的對(duì)象頭。

          static A a;
          public static void main(String[] args) throws Exception {
              a = new A();
              out.println("befre lock");
              out.println(ClassLayout.parseInstance(a).toPrintable());
              synchronized (a){
                  out.println("lock ing");
                  out.println(ClassLayout.parseInstance(a).toPrintable());
              }
              out.println("after lock");
              out.println(ClassLayout.parseInstance(a).toPrintable());
          }

          看結(jié)果:

          輕量級(jí)鎖的對(duì)象頭

          關(guān)于重量鎖首先看對(duì)象頭。

          static A a;
          public static void main(String[] args) throws Exception {
              //Thread.sleep(5000);
              a = new A();
              out.println("befre lock");
              out.println(ClassLayout.parseInstance(a).toPrintable());//無鎖

              Thread t1= new Thread(){
                  public void run() {
                      synchronized (a){
                          try {
                              Thread.sleep(5000);
                              System.out.println("t1 release");
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              t1.start();
              Thread.sleep(1000);
              out.println("t1 lock ing");
              out.println(ClassLayout.parseInstance(a).toPrintable());//輕量鎖
              sync();
              out.println("after lock");
              out.println(ClassLayout.parseInstance(a).toPrintable());//重量鎖
              System.gc();
              out.println("after gc()");
              out.println(ClassLayout.parseInstance(a).toPrintable());//無鎖---gc
          }

          public  static  void sync() throws InterruptedException {
              synchronized (a){
                  System.out.println("t1 main lock");
                  out.println(ClassLayout.parseInstance(a).toPrintable());//重量鎖
              }
          }

          看結(jié)果。

          輕量級(jí)鎖
          重量級(jí)鎖

          「由上述實(shí)驗(yàn)可總結(jié)下圖:」

          實(shí)驗(yàn)可總結(jié)

          性能對(duì)比偏向鎖和輕量級(jí)鎖:

          public class A {
              int i=0;
             
              public synchronized void parse(){
                  i++;
                  
              }
              //JOLExample6.countDownLatch.countDown();
          }

          執(zhí)行 1000000000L 次 ++ 操作。

          public class JOLExample4 {
              public static void main(String[] args) throws Exception {
                  A a = new A();
                  long start = System.currentTimeMillis();
                  //調(diào)用同步方法1000000000L 來計(jì)算1000000000L的++,對(duì)比偏向鎖和輕量級(jí)鎖的性能
                  //如果不出意外,結(jié)果灰常明顯
                  for(int i=0;i<1000000000L;i++){
                      a.parse();
                  }
                  long end = System.currentTimeMillis();
                  System.out.println(String.format("%sms", end - start));

              }
          }

          此時(shí)根據(jù)上面的測(cè)試可知是輕量級(jí)鎖,看下結(jié)果。

          輕量級(jí)鎖

          大概 16 秒。

          然后我們讓偏向鎖啟動(dòng)無延時(shí),在啟動(dòng)一次。

          -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

          再看下結(jié)果。

          偏向鎖啟動(dòng)無延時(shí)

          只需要 2 秒,速度提升了很多。

          再看下重量級(jí)鎖的時(shí)間。

          static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
          public static void main(String[] args) throws Exception {
              final A a = new A();

              long start = System.currentTimeMillis();

              //調(diào)用同步方法1000000000L 來計(jì)算1000000000L的++,對(duì)比偏向鎖和輕量級(jí)鎖的性能
              //如果不出意外,結(jié)果灰常明顯
              for(int i=0;i<2;i++){
                  new Thread(){
                      @Override
                      public void run() {
                          while (countDownLatch.getCount() > 0) {
                              a.parse();
                          }
                      }
                  }.start();
              }
              countDownLatch.await();
              long end = System.currentTimeMillis();
              System.out.println(String.format("%sms", end - start));

          }

          看下結(jié)果,大概 31 秒。

          synchronized 優(yōu)化的意義

          可以看出三種鎖的消耗是差距很大的,這也是 1.5 以后 synchronized 優(yōu)化的意義。

          需要注意的是如果對(duì)象已經(jīng)計(jì)算了 hashcode 就不能偏向了

          static A a;
          public static void main(String[] args) throws Exception {
              Thread.sleep(5000);
              a= new A();
              a.hashCode();
              out.println("befor lock");
              out.println(ClassLayout.parseInstance(a).toPrintable());
              synchronized (a){
                  out.println("lock ing");
                  out.println(ClassLayout.parseInstance(a).toPrintable());
              }
              out.println("after lock");
              out.println(ClassLayout.parseInstance(a).toPrintable());
          }

          看下結(jié)果。

          synchronized 優(yōu)化

          瀏覽 68
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    亚洲操逼网站豆花 | 黄色成人在线 | 日韩黄色片在线观看 | 已婷婷狠狠18禁久久YY | 波多野结衣视频免费在线观看 |