循环中的引用重用

时间:2017-02-15 13:42:02

标签: java jvm

jvm是否重用现有的引用,以防它在循环内部或在每次迭代中创建新的引用? 示例:

for (SomeObject o : collectionsOfObjects){
  Date temporaryDate = o.getDate();
}

VS

   Date temporaryDate = null;
   for (SomeObject o : collectionsOfObjects){
     temporaryDate = o.getDate();
    }

只是为了确保我想知道第一个例子是否会导致更多的内存消耗,因为每个循环我们都会创建Date类型的新引用,或者jvm可以在下面对它进行优化并在每次迭代时使用相同的引用。

1 个答案:

答案 0 :(得分:0)

局部变量的范围是编译时工件。当编译器(例如javac)从源代码生成字节码时,此信息已丢失。

Java字节码通过位置编号引用stack frame内的存储,相当于局部变量。将局部变量分配给这些位置以及计算堆栈帧中所需的最大存储量是在编译时完成的。

编译器可以在堆栈帧中重用具有析取范围的变量的存储,但不需要(通常这样做)。但在您的示例中,没有带有析取范围的变量。在循环体内,同时存在三个变量temporaryDateo和一个包含Iterator的未命名变量。由于存在这些变量同时存在的点,因此它们必须具有不同的存储位置,并且堆栈帧的大小必须至少为3。这适用于两种变体,因此没有相关差异。

但由于堆栈帧中的存储只能用于具有析取范围的变量,因此限制变量的范围(无论如何这是一种好的做法)可以改善重用,例如,当你写作

for(SomeObject o : collectionsOfObjects){
  Date temporaryDate = o.getDate();
}
for(OtherObject other : collectionsOfObjects){
  String n = other.getName();
}

第一个循环所需的所有三个变量的存储可以在第二个循环中重复使用(不同类型无关紧要)。如果在循环外声明了temporaryDate,那么在第二个循环中重用它的存储是不可能的。

请注意,堆栈帧是在方法入口处分配的,并且无论如何都要在方法出口处销毁。局部变量的声明不会导致分配或任何操作。在运行时,只有实际的读写才有意义。因此,声明变量的方式对性能没有影响。

当JVM开始优化代码时(谈论像HotSpot / OpenJDK这样复杂的JVM),它会将代码转换为Static single assignment form,这可能与您的声明完全不同。这种形式可以实现基本优化,例如完全删除未使用的变量,将可预测的折叠变量保持为一个,并消除冗余计算或用预测结果替换它们。