我知道类似的问题之前已被多次询问,但我仍然不相信对象何时符合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个对象。在方法一中,每个对象都有一个引用(myObject),因此它们不符合GC的条件?
在每个循环迭代的方法二中,您将从上一次迭代中创建的对象中删除引用。所以在第一次循环迭代之后,对象肯定会开始变得合格。
或者它是性能和代码可读性之间的权衡。可维护性?
我误解了什么?
注意: 假设我关心性能,循环后不需要myObject。
提前致谢
答案 0 :(得分:5)
如果有例如方法一中将创建100000个项目,然后是100000个对象,每个对象都有一个引用(myObject),因此它们不符合GC的条件?
不,从垃圾收集器的角度来看,两种方法的工作方式相同,即没有内存泄漏。使用方法二,只要以下语句运行
myObject = new MyObject();
被引用的前一个MyObject
变成了一个孤儿(除非你使用Object
传递它,比如说,保存那个引用的另一个方法),并且有资格进行垃圾回收。
不同之处在于,一旦循环用尽,您可以通过最初在循环外创建的MyObject
引用仍然可以访问myObject
的最后一个实例。
GC是否知道循环执行期间引用何时超出范围,或者只能在方法结束时知道?
首先,只有一个参考,而不是参考。它是循环中未被引用的对象。其次,垃圾收集不会自发地开始。所以忘记循环,当方法退出时甚至可能不会发生。
请注意,我说,孤儿对象有资格获得gc ,而不是立即收集它们。垃圾收集永远不会实时发生,它会分阶段发生。在标记阶段,不通过实时线程可以访问的所有对象都被标记为删除。然后在扫描阶段,回收内存并进行额外压缩,就像对硬盘进行碎片整理一样。因此,它更像是一批而非零碎的操作。
GC并未对此范围或方法感到困扰。它只查找未引用的对象,当它感觉就像这样做时它会这样做。你不能强迫它。您可以确定的唯一事情是,如果JVM内存不足,GC将会运行,但是您无法确切地确定它是什么时候。
但是,所有这些不意味着GC在方法执行时甚至在循环运行时都无法启动。如果你有一个消息处理器每10分钟左右处理10,000条消息,然后睡觉,即 bean 在循环内等待,进行10,000次迭代,然后再等一下;即使该方法还没有完成,GC肯定会采取行动收回记忆。
答案 1 :(得分:3)
您误解了对象何时符合GC条件 - 当它们无法再从活动线程访问时,它们会执行此操作。在这种情况下,这意味着:
因此,无论使用哪种方法,MyObject的实例都有资格在每次循环迭代结束时使用GC。两种方法之间的差异(理论上)是JVM必须在方法1中的每次迭代中为新对象引用分配内存,而不是在方法2中。但是,这假定Java编译器和/或即时编译器优化方法1实际上就像方法2一样,并不聪明。
在任何情况下,我都会采用更具可读性且不易出错的方法1:
答案 2 :(得分:0)
我不希望在块内声明变量会对性能产生不利影响。
至少在理论上,JVM在方法的开头分配堆栈帧并在结束时销毁它。暗示将具有容纳所有局部变量的累积大小。
见这里的2.6节: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html
这与其他语言(例如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
的引用,这是一个不整洁和不必要的麻烦(和tinsy weenie开销)。
毫无疑问,如果(并且仅当)您不需要在循环外访问它们,您应该在循环内声明变量。
IMHO阻止声明(如bar
上面所述)已被使用。
如果方法分阶段进行,您可以使用块保护后期阶段免受可变污染。
使用合适的(简短)注释,通常可以更加可读(和有效)的方式来构建代码,而不是将其丢失为私有方法。
我有一个粗略的算法(Hashlife),在方法中使早期的工件可用于垃圾收集可以在到达结束和获得OutOfMemoryError
之间产生差异。
答案 3 :(得分:0)
在两种方法中,对象都会收集垃圾。
在方法1中:当循环退出时,循环内的所有局部变量都会收集垃圾,因为循环结束。
在方法2中:当为myObject变量分配新的新引用时,前面没有正确的引用。所以之前得到垃圾,等等,直到循环运行。
所以在这两种方法中都没有性能瓶颈。