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%。
添加回答
举报