1 回答

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