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