在java中,变量应该在循环内部还是在循环外部声明

在java中,变量应该在循环内部还是在循环外部声明,java,performance,garbage-collection,scope,Java,Performance,Garbage Collection,Scope,我知道类似的问题以前已经被问过很多次了,但我仍然不确定对象何时符合GC的条件,以及哪种方法更有效 方法一: for (Item item : items) { MyObject myObject = new MyObject(); //use myObject. } 方法二: MyObject myObject = null; for (Item item : items) { myObject = new MyObject(); //use myObject.

我知道类似的问题以前已经被问过很多次了,但我仍然不确定对象何时符合GC的条件,以及哪种方法更有效

方法一:

for (Item item : items) {
    MyObject myObject = new MyObject();
    //use myObject.
}
方法二:

MyObject myObject = null;
for (Item item : items) {
    myObject = new MyObject();
    //use myObject.
}
我理解:“通过最小化局部变量的范围,可以提高代码的可读性和可维护性,并降低出错的可能性”。(约书亚·布洛赫)

但是性能/内存消耗又如何呢?在Java中,当没有对对象的引用时,对象被垃圾收集。如果有100000个项目,则将创建100000个对象。在方法1中,每个对象都有一个引用(myObject),所以它们不符合GC

在方法2中,每次循环迭代都要从上一次迭代中创建的对象中删除引用。因此,在第一次循环迭代之后,对象肯定开始变得合格

或者它是性能与代码可读性和可维护性之间的折衷

我误解了什么

注: 假设我关心性能,并且在循环之后不需要myObject

提前谢谢

如果有100000个项目,那么将在方法1中创建100000个对象,每个对象都将有一个引用(myObject),因此它们不符合GC

不,从垃圾收集器的角度来看,这两种方法工作相同,即没有内存泄漏。对于方法2,只要以下语句运行

myObject = new MyObject();
被引用的上一个
MyObject
将成为孤立对象(除非在使用该
对象时,您将其传递给另一个保存该引用的方法),并且符合垃圾收集的条件

不同之处在于,一旦循环结束,您仍然可以通过最初在循环外部创建的
MyObject
引用访问
MyObject
的最后一个实例



GC是否知道在循环执行期间引用何时超出范围,或者它只能在方法末尾知道

首先,只有一个参考文献,不是参考文献。是对象在循环中未被引用。其次,垃圾收集并不是自发开始的。因此,忘记循环吧,当方法退出时,它甚至可能不会发生

请注意,我说过,孤立对象符合gc的条件,而不是立即收集它们。垃圾收集从不实时进行,而是分阶段进行的。在标记阶段,所有通过活动线程无法访问的对象都被标记为删除。然后在扫描阶段,内存被回收并额外压缩,就像对硬盘进行碎片整理一样。因此,它更像是一个批处理,而不是零碎的操作

GC并不关心作用域或方法本身。它只查找未引用的对象,并在需要时进行查找。你不能强迫它。唯一可以确定的是,如果JVM内存不足,GC将运行,但您无法确定它将在何时运行


但是,所有这些并不意味着GC不能在方法执行时,甚至在循环运行时启动。比如说,如果您有一个消息处理器,它每10分钟左右处理10000条消息,然后在其间休眠,即bean在循环中等待,进行10000次迭代,然后再次等待;GC肯定会采取行动来回收内存,即使该方法尚未运行到完成阶段。

您误解了对象何时符合GC的条件-当从活动线程无法再访问它们时,它们会这样做。在这方面,这意味着:

  • 当对它们的唯一引用超出范围时(方法1)
  • 当对它们的唯一引用被赋予另一个值时(方法2)
因此,无论使用哪种方法,MyObject的实例在每个循环迭代结束时都有资格使用GC。这两种方法(理论上)的区别在于,JVM必须在方法1中的每次迭代中为一个新的对象引用分配内存,而不是在方法2中。但是,这假设Java编译器和/或即时编译器不适合将方法1优化为实际的方法2

在任何情况下,我都会选择可读性更强、不易出错的方法1,理由是:

  • 单对象引用分配的性能开销很小
  • 无论如何,它可能会得到优化

我不希望在块内声明变量会对性能产生有害影响

至少在理论上,JVM在方法开始时分配堆栈帧,并在方法结束时销毁它。隐含地,将具有容纳所有局部变量的累积大小

见此处第2.6节:

这与其他语言(如C)是一致的,在C语言中,函数/方法执行时调整堆栈帧的大小是一种开销,没有明显的返回

所以无论你在哪里申报,都不会有什么不同

实际上,在块中声明变量可以帮助编译器认识到堆栈帧的有效大小可以更小:

void foo() {
   int x=6;
   int y=7;
   int z=8;

  //.....
}

void bar() {
   { 
     int x=6;
     //....
   }
   {
     int y=7;
     //....
   }
   {
     int z=8;
     //....
   }
 }
请注意,
bar()
显然只需要一个局部变量,而不是3个

尽管缩小堆栈帧不太可能对性能产生任何实际影响

但是,当引用超出范围时,可能会使其引用的对象可用于垃圾收集。否则,您将需要设置对
null
的引用,这是一个不整洁和不必要的麻烦(以及微不足道的开销)

毫无疑问,如果(且仅当)不需要在循环外部访问变量,则应在循环内部声明变量。

IMHO阻塞语句(如<代码