<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>

          從字節(jié)碼角度分析一波 Spring 源碼中隱藏知識點!

          共 17837字,需瀏覽 36分鐘

           ·

          2021-08-27 19:08

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

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

          編輯:業(yè)余草

          blog.csdn.net/w605283073

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

          一、背景

          這周一我面試了一個 Java 程序員,工作了 6 年了。我問的第一個問題就是“Redis 能用來做什么?你們系統(tǒng)中使用了 Redis 的哪些功能?”,他回答說是緩存

          緩存確實是 Redis 使用最多的領(lǐng)域。可當(dāng)我再進(jìn)一步問“還有呢?”,他想了一會說:“分布式鎖”。然后我就分布式鎖再深入問下去,他開始搖頭:我們項目里面 Redis 的鎖方法都是別人(應(yīng)該是架構(gòu)師)封裝好的,拿過來直接使用,內(nèi)部細(xì)節(jié)沒有去了解過,也沒有必要了解

          然后我和他說了,Redis 有很多使用場景,參考《面試官:說出16個Redis常見的使用場景!》。

          然后我和他講了庖丁解牛和蠻夫剁骨頭的故事,他認(rèn)為船到橋頭自然直,他沒有用和學(xué) Redis 的這些功能是因為項目中沒用使用的場景和必要。

          可能很多人會覺得沒必要,因為平時開發(fā)用不到,而且不學(xué)這個也沒耽誤學(xué)習(xí)

          但是這里分享一點感悟,即人總是根據(jù)自己已經(jīng)掌握的知識和技能來解決問題的

          這里有個悖論,有時候你覺得有些技術(shù)沒用恰恰是因為你沒有熟練掌握它,遇到可以使用它的場景你根本想不到用。

          從生活的角度來講

          如果你是一個非計算機(jī)專業(yè)的學(xué)生,你老師給你幾張圖書的拍照,大概 3000 字,讓你打印成文字。

          你打開電腦,噼里啪啦一頓敲,搞了一下午干完了。

          如果你知道語音輸入,那么你可能采用語音輸入的方式,30 分鐘搞定。

          如果你了解 OCR 圖片文字識別,可能 5 分鐘搞定。

          不同的方法,帶來的效果完全不同。然而最可怕的是,你不會語音輸入或者 OCR,你不會覺得自己少了啥。

          OCR 識別絕對不是你提高點打字速度可以追趕上的。

          學(xué)習(xí) Java 的角度

          很多人學(xué)習(xí)知識主要依賴百度,依賴博客,依賴視頻和圖書,而且這些資料質(zhì)量參差不齊,而且都是別人理解之后的結(jié)果。

          比如你平時不怎么看源碼,那么你就很少能將源碼作為你學(xué)習(xí)的素材,只能依賴博客、圖書、視頻等。

          如果你平時喜歡看源碼,你會對源碼有自己的理解,你會發(fā)現(xiàn)源碼對你的學(xué)習(xí)有很多幫助。

          如果你平時不怎么用反編譯和反匯編,那么你更多地只能依賴源碼,依賴調(diào)試等學(xué)習(xí)知識,而不能從字節(jié)碼層面來學(xué)習(xí)和理解知識。

          當(dāng)你慢慢熟練讀懂虛擬機(jī)指令,你會發(fā)現(xiàn)你多了一個學(xué)習(xí)知識的途徑。

          昨天的文章

          昨天我發(fā)的那篇文章(Java For循環(huán)的十一種優(yōu)化方案!),里面也有很多知識點,但是很多人看不上,因為單個優(yōu)化的效果并不明顯。但是你要知道 HikariCP 之所以成為最快的數(shù)據(jù)庫,就是因為作者的重點細(xì)小優(yōu)化做到的。

          LongAdder 之所以性能牛叉(面試官:為什么LongAdder性能比long還快),也是因為作者不但的研究和突破,分而治之的通過細(xì)微的調(diào)整,將 AtomicLong 遠(yuǎn)遠(yuǎn)甩在身后,而作者的論文也在業(yè)界享有盛譽!

          人總是不愿意離開舒適區(qū)的

          很多人在學(xué)習(xí)新知識時,總是本能地抵觸。會找各種理由不去學(xué),“比如暫時用不到”,“學(xué)了沒啥用”,“以后再說”。

          甚至認(rèn)為這是在浪費時間。

          為什么要學(xué)習(xí)字節(jié)碼?

          這幾年我 Watch 了不少知名開源框架,通過它們,我學(xué)習(xí)了一些 JVM 字節(jié)碼的知識(深入理解JVM方法調(diào)用的內(nèi)部機(jī)制),雖然不算精通,但是讀字節(jié)碼起來已經(jīng)不太吃力。

          因此,我認(rèn)為學(xué)會字節(jié)碼可以從比源碼更深的層面去學(xué)習(xí) Java 相關(guān)知識。

          雖然不可能所有問題都用字節(jié)碼的知識來解決,但是它給你一個學(xué)習(xí)的途徑。

          比如通過字節(jié)碼的學(xué)習(xí)你可以更好地理解 Java中各種語法和語法糖背后的原理,更好地理解多態(tài)等語言特性。

          語法糖

          下面通過舉一個簡單的例子,來說明學(xué)習(xí)字節(jié)碼的作用。

          public class ForEachDemo {

              public static void main(String[] args) {

                  List<String> data = new ArrayList<>();
                  data.add("a");
                  data.add("b");
                  for (String str : data) {
                      System.out.println(str);
                  }
              }
          }

          編譯:javac ForEachDemo.java

          反匯編:javap -c ForEachDemo

          public class com.imooc.basic.learn_source_code.local.ForEachDemo {
            public com.imooc.basic.learn_source_code.local.ForEachDemo();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4return

            public static void main(java.lang.String[]);
              Code:
                 0new           #2                  // class java/util/ArrayList
                 3: dup
                 4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
                 7: astore_1
                 8: aload_1
                 9: ldc           #4                  // String a
                11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
                16: pop
                17: aload_1
                18: ldc           #6                  // String b
                20: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
                25: pop
                26: aload_1
                27: invokeinterface #7,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
                32: astore_2
                33: aload_2
                34: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
                39: ifeq          62
                42: aload_2
                43: invokeinterface #9,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
                48: checkcast     #10                 // class java/lang/String
                51: astore_3
                52: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
                55: aload_3
                56: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                59: goto          33
                62return
          }

          我們可以清晰地看到 foreach 循環(huán)底層用到了迭代器實現(xiàn),甚至可以逆向腦補出對應(yīng)的 Java 源碼(大家可以嘗試根據(jù)字節(jié)碼寫出等價的源碼)。

          讀源碼遇到的一個問題

          我們在讀源碼時經(jīng)常會遇到類似下面的這種寫法。

          org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#startWebServer中的源碼:

           private WebServer startWebServer() {
            WebServer webServer = this.webServer;
            if (webServer != null) {
             webServer.start();
            }
            return webServer;
           }

          在函數(shù)中聲明一個和成員變量同名的局部變量,然后將成員變量賦值給局部變量,再去使用。

          看似很小的細(xì)節(jié),隱含著一個優(yōu)化思想

          可能有些人讀過某些文章有提到(為什么我們總得看到一個文章學(xué)會一個知識?如果沒看到怎么辦?),更多的人可能并不能理解有什么優(yōu)化。

          模擬

          普通的語法糖這里就不做過多展開,重點講講第二個優(yōu)化的例子。

          模仿上述寫法的例子:

          public class LocalDemo {

              private List<String> data = new ArrayList<>();

              public void someMethod(String param) {
                  List<String> data = this.data;
                  if (data != null && data.size() > 0 && data.contains(param)) {
                      System.out.println(data.indexOf(param));
                  }

              }

          }

          編譯:javac LocalDemo.java

          反匯編:javap -c LocalDemo

          public class com.imooc.basic.learn_source_code.local.LocalDemo {
            public com.imooc.basic.learn_source_code.local.LocalDemo();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4: aload_0
                 5new           #2                  // class java/util/ArrayList
                 8: dup
                 9: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
                12: putfield      #4                  // Field data:Ljava/util/List;
                15return

            public void someMethod(java.lang.String);
              Code:
                 0: aload_0
                 1: getfield      #4                  // Field data:Ljava/util/List;
                 4: astore_2
                 5: aload_2
                 6: ifnull        41
                 9: aload_2
                10: invokeinterface #5,  1            // InterfaceMethod java/util/List.size:()I
                15: ifle          41
                18: aload_2
                19: aload_1
                20: invokeinterface #6,  2            // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z
                25: ifeq          41
                28: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
                31: aload_2
                32: aload_1
                33: invokeinterface #8,  2            // InterfaceMethod java/util/List.indexOf:(Ljava/lang/Object;)I
                38: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
                41return
          }

          此時局部變量表中 0 為 this,1 為 param  2 為 局部變量 data。

          Java局部變量表

          直接使用成員變量的例子:


          public class ThisDemo {


              private List<String> data = new ArrayList<>();

              public void someMethod(String param) {

                  if (data != null && data.size() > 0 && data.contains(param)) {
                      System.out.println(data.indexOf(param));
                  }

              }
          }

          編譯:javac ThisDemo.java

          反匯編:javap -c ThisDemo

          public class com.imooc.basic.learn_source_code.local.ThisDemo {
            public com.imooc.basic.learn_source_code.local.ThisDemo();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4: aload_0
                 5new           #2                  // class java/util/ArrayList
                 8: dup
                 9: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
                12: putfield      #4                  // Field data:Ljava/util/List;
                15return

            public void someMethod(java.lang.String);
              Code:
                 0: aload_0
                 1: getfield      #4                  // Field data:Ljava/util/List;
                 4: ifnull        48
                 7: aload_0
                 8: getfield      #4                  // Field data:Ljava/util/List;
                11: invokeinterface #5,  1            // InterfaceMethod java/util/List.size:()I
                16: ifle          48
                19: aload_0
                20: getfield      #4                  // Field data:Ljava/util/List;
                23: aload_1
                24: invokeinterface #6,  2            // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z
                29: ifeq          48
                32: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
                35: aload_0
                36: getfield      #4                  // Field data:Ljava/util/List;
                39: aload_1
                40: invokeinterface #8,  2            // InterfaceMethod java/util/List.indexOf:(Ljava/lang/Object;)I
                45: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
                48return
          }

          此時局部變量表只有兩個,即 this 和 param。

          局部變量表

          大家也可以通過 javap -c -v 來查看更詳細(xì)信息,本例截圖中用到 IDEA 插件為jclasslib bytecode viewer

          分析

          通過源碼其實我們并不能很好的理解到底優(yōu)化了哪里。

          我們分別對兩個類進(jìn)行編譯和反匯編后可以清晰地看到:第一個例子代碼多了一行,反而反編譯后的字節(jié)碼更短。

          第二個例子反編譯后的字節(jié)碼比第一個例子長在哪里呢?

          我們發(fā)現(xiàn)主要多在:getfield #4 // Field data:Ljava/util/List;這里。

          即每次獲取 data 對象都要先aload_0然后再getfield指令獲取。

          第一個例子通過astore_2將其存到了局部變量表中,每次用直接aload_2直接從局部變量表中加載到操作數(shù)棧。

          從而不需要每次都從 this 對象中獲取這個屬性,因此效率更高。

          這種思想有點像寫代碼中常用的緩存,即將最近要使用的數(shù)據(jù)先查一次緩存起來,使用時優(yōu)先查緩存。

          「本質(zhì)上體現(xiàn)了操作系統(tǒng)中的時間局部性和空間局部性的概念(不懂的話翻下書或百度下)」

          「因此通過字節(jié)碼的分析,通過聯(lián)系實際的開發(fā)經(jīng)驗,通過聯(lián)系專業(yè)知識,這個問題我們就搞明白了。」

          另外也體現(xiàn)了用空間換時間的思想。

          知識只有能貫穿起來,理解的才能更牢固。

          此處也體現(xiàn)出專業(yè)基礎(chǔ)的重要性。

          另外知識能聯(lián)系起來、思考到本質(zhì),理解才能更深刻,記憶才能更牢固,才更有可能靈活運用。

          四、總結(jié)

          這只是其中一個非常典型的例子,學(xué)習(xí) JVM 字節(jié)碼能夠給你一個不一樣的視角,讓你多一個學(xué)習(xí)的途徑。

          可能很多人說自己想學(xué)但是無從下手,這里推薦大家先看《深入理解Java虛擬機(jī)》,然后結(jié)合《Java虛擬機(jī)規(guī)范》,平時多敲一下 javap 指令,慢慢就熟悉了,另外強力推薦jclasslib bytecode viewer插件,該插件可以點擊指令跳轉(zhuǎn)到 Java 虛擬機(jī)規(guī)范對該指令的介紹的部分,對學(xué)習(xí)幫助極大。

          很多人可能會說,學(xué)這個太慢。

          的確,急于求成怎么能學(xué)的特別好呢?厚積才能薄發(fā),耐不住寂寞怎么能學(xué)有所成呢。

          本文通過這其中一個例子讓大家理解,JVM字節(jié)碼可以幫助大家理解Java的一些語法(篇幅有限,而且例子太多,這里就不給出了,感興趣的同學(xué)自己嘗試),甚至幫助大家學(xué)習(xí)源碼。

          試想一下,如果你認(rèn)為學(xué)習(xí)字節(jié)碼無用,甚至你都不了解,你怎么可能用它來解決問題呢?

          「你所掌握的知識幫助你成長由限制了你的成長,要敢于突破舒適區(qū),給自己更多的成長機(jī)會。」

          歡迎點贊、評論、轉(zhuǎn)發(fā),你的鼓勵,是我創(chuàng)作的動力。

          瀏覽 76
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  操B网站视频 | 久久久久久网站 | 女av大奶 | 国产蜜臀在线观看 | 99久久婷婷豆花 |