<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 41 突破難點:如何看 Lambda 源碼 ## 引導語 大家都知道 Java8 中新增了 Lambda 表達式,使用 Lambda 表達式可以對代碼進行大量的優化,用幾行代碼就可以做很多事情,本章以 Lambda 為例,第一小節說明一下其底層的執行原理,第二小節說明一下 Lambda 流在工作中常用的姿勢。 ### 1 Demo 首先我們來看一個 Lambda 表達式的 Demo,如下圖: ![](https://img.kancloud.cn/c4/52/c452000b5a5760efd79a3ae9e867d634_741x490.jpg) 代碼比較簡單,就是新起一個線程打印一句話,但對于圖中 () -> System.out.println ( “ lambda is run “ ) 這種代碼,估計很多同學都感覺到很困惑,Java 是怎么識別這種代碼的? 如果我們修改成匿名內部類的寫法,就很清楚,大家都能看懂,如下圖: ![](https://img.kancloud.cn/cd/26/cd268d57aad61c145054d833028bf708_695x555.jpg) 那是不是說 () -> System.out.println ( “ lambda is run “ ) 這種形式的代碼,其實就是建立了內部類呢?其實這就是最簡單 Lambda 表達式,我們是無法通過 IDEA 看到源碼和其底層結構的,下面我們就來介紹幾種可看到其底層實現的方式。 ### 2 異常判斷法 我們可以在代碼執行中主動拋出異常,打印出堆棧,堆棧會說明其運行軌跡,一般這種方法簡單高效,基本上可以看到很多情況下的隱藏代碼,我們來試一下,如下圖: ![](https://img.kancloud.cn/c7/1e/c71ea4d140ac005a75f976571ca1559d_765x697.jpg) 從異常的堆棧中,我們可以看到 JVM 自動給當前類建立了內部類(錯誤堆棧中出現多次的 $ 表示有內部類),內部類的代碼在執行過程中,拋出了異常,但這里顯示的代碼是 Unknown Source,所以我們也無法 debug 進去,一般情況下,異常都能暴露出代碼執行的路徑,我們可以打好斷點后再次運行,但對于 Lambda 表達式而言,通過異常判斷法我們只清楚有內部類,但無法看到內部類中的源碼。 ### 3 javap 命令法 javap 是 Java 自帶的可以查看 class 字節碼文件的工具,安裝過 Java 基礎環境的電腦都可以直接執行 javap 命令,如下圖: ![](https://img.kancloud.cn/e8/86/e8864187839dd8f0844069421abf70b0_916x568.jpg) 命令選項中,我們主要是用-v -verbose 這個命令,可以完整輸出字節碼文件的內容。 接下來我們使用 javap 命令查看下 Lambda.class 文件,在講解的過程中,我們會帶上一些關于 class 文件的知識。 我們在命令窗口中找到 Lambda.class 所在的位置,執行命令:javap -verbose Lambda.class,然后你會看到一長串的東西,這些叫做匯編指令,接下來我們來一一講解下( 所有的參考資料來自 Java 虛擬機規范,不再一一引用說明): 匯編指令中我們很容易找到 Constant pool 打頭的一長串類型,我們叫做常量池,官方英文叫做 Run-Time Constant Pool,我們簡單理解成一個裝滿常量的 table ,table 中包含編譯時明確的數字和文字,類、方法和字段的類型信息等等。table 中的每個元素叫做 cpinfo,cpinfo 由唯一標 識 ( tag ) + 名稱組成,目前 tag 的類型一共有: ![](https://img.kancloud.cn/a1/90/a190e236c10d2cb0853716f7972ce116_502x734.jpg) 貼出我們解析出來的部分圖: ![](https://img.kancloud.cn/11/c6/11c66ddfd92620e4f223603546516e4a_1873x940.jpg) 1. 圖中 Constant pool 字樣代表當前信息是常量池; 2. 每行都是一個 cp_info ,第一列的 #1 代表是在常量池下標為 1 的位置 ; 3. 每行的第二列,是 cp_info 的唯一標識 ( tag ) ,比如 Methodref 對應著上表中的 CONSTANT_Methodref(上上圖中表格中 value 對應 10 的 tag),代表當前行是表示方法的描述信息的,比如說方法的名稱,入參類型,出參數類型等,具體的含義在 Java 虛擬機規范中都可以查詢到,Methodref 的截圖如下: ![](https://img.kancloud.cn/f7/3b/f73b0bb161c8df440f4d4560deee5357_1943x1449.jpg) 4. 每行的第三列,如果是具體的值的話,直接顯示具體的值,如果是復雜的值的話,會顯示 cp_info 的引用,比如說圖中標紅 2 處,引用兩個 13 和 14 位置的 cp_info,13 表示方法名字是 init,14 表示方法無返回值,結合起來表示方法的名稱和返回類型,就是一個無參構造器; 5. 每行的第四列,就是具體的值了。 對于比較重要的 cp_info 類型我們說明下其含義: 1. InvokeDynamic 表示動態的調用方法,后面我們會詳細說明; 2. Fieldref 表示字段的描述信息,如字段的名稱、類型; 3. NameAndType 是對字段和方法類型的描述; 4. MethodHandle方法句柄,動態調用方法的統稱,在編譯時我們不知道具體是那個方法,但運行時肯定會知道調用的是那個方法; 5. MethodType 動態方法類型,只有在動態運行時才會知道其方法類型是什么。 我們從上上圖中標紅的 3 處,發現 Ljava/lang/invoke/MethodHandles$Lookup,java/lang/invoke/LambdaMetafactory.metafactory 類似這樣的代碼,MethodHandles 和 LambdaMetafactory 都是 java.lang.invoke 包下面的重要方法,invoke 包主要實現了動態語言的功能,我們知道 java 語言屬于靜態編譯語言,在編譯的時候,類、方法、字段等等的類型都已經確定了,而 invoke 實現的是一種動態語言,也就是說編譯的時候并不知道類、方法、字段是什么類型,只有到運行的時候才知道。 比如這行代碼:Runnable runnable = () -> System.out.println(“lambda is run”); 在編譯器編譯的時候 () 這個括號編譯器并不知道是干什么的,只有在運行的時候,才會知道原來這代表著的是 Runnable.run() 方法。invoke 包里面很多類,都是為了代表這些 () 的,我們稱作為方法句柄( MethodHandler ),在編譯的時候,編譯器只知道這里是個方法句柄,并不知道實際上執行什么方法,只有在執行的時候才知道,那么問題來了,JVM 執行的時候,是如何知道 () 這個方法句柄,實際上是執行 Runnable.run() 方法的呢? 首先我們看下 simple 方法的匯編指令: ![](https://img.kancloud.cn/b9/70/b9705daee85a385bdcaa8dcf0c35811d_765x528.jpg) 從上圖中就可以看出 simple 方法中的 () -> System.out.println(“lambda is run”) 代碼中的 (),實際上是Runnable.run 方法。 我們追溯到 # 2 常量池,也就是上上圖中標紅 1 處,InvokeDynamic 表示這里是個動態調用,調用的是兩個常量池的 cp_info,位置是 #0:#37 ,我們往下找 #37 代表著是 // run:()Ljava/lang/Runnable,這里表明了在 JVM 真正執行的時候,需要動態調用 Runnable.run() 方法,從匯編指令上我們可以看出 () 實際上就是 Runnable.run(),下面我們 debug 來證明一下。 我們在上上圖中 3 處發現了 LambdaMetafactory.metafactory 的字樣,通過查詢官方文檔,得知該方法正是執行時, 鏈接到真正代碼的關鍵,于是我們在 metafactory 方法中打個斷點 debug 一下,如下圖: ![](https://img.kancloud.cn/fe/ff/feff7439808bfa9ada9a2a568998380f_1797x859.jpg) metafactory 方法入參 caller 代表實際發生動態調用的位置,invokedName 表示調用方法名稱,invokedType 表示調用的多個入參和出參,samMethodType 表示具體的實現者的參數,implMethod 表示實際上的實現者,instantiatedMethodType 等同于 implMethod。 以上內容總結一下: 1:從匯編指令的 simple 方法中,我們可以看到會執行 Runnable.run 方法; 2:在實際的運行時,JVM 碰到 simple 方法的 invokedynamic 指令,會動態調用 LambdaMetafactory.metafactory 方法,執行具體的 Runnable.run 方法。 所以可以把 Lambda 表達值的具體執行歸功于 invokedynamic JVM 指令,正是因為這個指令,才可以做到雖然編譯時不知道要干啥,但動態運行時卻能找到具體要執行的代碼。 接著我們看一下在匯編指令輸出的最后,我們發現了異常判斷法中發現的內部類,如下圖: ![](https://img.kancloud.cn/f3/cc/f3cc5812b4fb6b52544bbfbabc114141_1348x1047.jpg) 上圖中箭頭很多,一層一層的表達清楚了當前內部類的所有信息。 ### 4 總結 我們總結一下,Lambda 表達式執行主要是依靠 invokedynamic 的 JVM 指令來實現,咱們演示的類的全路徑為:demo.eight.Lambda 感興趣的同學可以自己嘗試一下。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看