1 回答

TA貢獻1877條經驗 獲得超6個贊
您的問題的答案在于 JVM 規范,特別是您指出的不同之處:指令dup
(JVMS §6.5.dup)。從那些文檔:
復制操作數棧頂部的值并將復制的值壓入操作數棧。
查看操作數堆棧文檔(JVMS §2.6.2,重點添加):
少量 Java 虛擬機指令(
dup
指令 (§dup) 和swap
(§swap))作為原始值在運行時數據區域上運行,而不考慮它們的特定類型;這些指令的定義方式使其不能用于修改或分解單個值。這些對操作數堆棧操作的限制是通過類文件驗證(§4.10)強制執行的。
再深入一層,查看類驗證部分(JVMS §4.10,重點添加):
鏈接時驗證增強了運行時解釋器的性能。可以消除在運行時為每條解釋指令驗證約束而必須執行的昂貴檢查。Java 虛擬機可以假定這些檢查已經執行。
這表明這些限制是在鏈接時驗證的,也就是 JVM 加載您的類文件時。所以回答你的問題:
使用這些結構背后的真正原因是什么?
讓我們剖析一下指令在每種情況下的作用:
在第一種情況下(使用說明dup
):
invokevirtual
將結果存儲在操作數棧的頂部dup
重復所以現在在堆棧頂部有兩個結果副本astore_2
將其存儲到局部變量 #2 中,該變量從操作數堆棧中彈出一個引用ifnull
檢查操作數棧的頂部是否為空,如果是,則轉到指令 15,否則繼續(我們假設它不為空)aload_2
將局部變量#2 推入操作數棧的頂部invokevirtual
在操作數棧的頂部調用一個方法,彈出它,然后壓入結果ireturn
從操作數棧彈出頂部值并返回它
在第二種情況下:
invokevirtual
將結果存儲在操作數棧的頂部astore_2
將結果彈出操作數棧并將其存儲在局部變量 #2 中aload_2
將局部變量#2 推入操作數棧的頂部ifnull
檢查操作數棧的頂部是否為空,如果是,則轉到指令 15,否則繼續(我們假設它不為空)aload_2
將局部變量#2 推入操作數棧的頂部invokevirtual
在操作數棧的頂部調用一個方法,彈出它,然后壓入結果ireturn
從操作數棧彈出頂部值并返回它
那么有什么區別呢?第一個調用aload_2
一次又一次dup
,第二個只調用aload
兩次。這里的區別幾乎沒有。如果查看整個操作過程中堆棧的大小,您會發現第一個實現將操作數堆棧增加了一個額外的值(少于 10 個字節,通常為 8 或 4 個字節,具體取決于 64 位或 32 位 JVM ), 但從堆棧內存中加載的局部變量少了一個。第二個使操作數堆棧稍微小一些,但有一個額外的局部變量加載(讀?。簭膬却嬷蝎@取)。
歸根結底,這些優化的影響非常小,除非是在內存極低的應用程序中,例如嵌入式系統。那么對你來說?做可讀的事情。
如有疑問:“過早優化(可能)是萬惡之源?!?除非您知道您的代碼很慢或者可以在運行之前證明它很慢,否則最好編寫可讀的代碼。這幾乎不屬于您應該提前優化的關鍵 3%。
添加回答
舉報