在Java中使用final变量是否可以改善垃圾收集?

Goran Martinic 82 java garbage-collection final

今天,我和我的同事讨论了finalJava中关键字的用法,以改进垃圾收集.

例如,如果您编写如下方法:

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

声明方法中的变量final将有助于垃圾收集在方法退出后从方法中未使用的变量清除内存.

这是真的?

benjismith.. 83

这是一个稍微不同的示例,一个具有最终引用类型字段,而不是最终值类型局部变量:

public class MyClass {

   public final MyOtherObject obj;

}

每次创建MyClass实例时,您都将创建对MyOtherObject实例的传出引用,并且GC必须按照该链接查找实时对象.

JVM使用标记扫描GC算法,该算法必须检查GC"根"位置中的所有实时引用(如当前调用堆栈中的所有对象).每个活动对象被"标记"为活着,并且活动对象引用的任何对象也被标记为活着.

完成标记阶段后,GC扫描堆,释放所有未标记对象的内存(并为剩余的活动对象压缩内存).

此外,重要的是要认识到Java堆内存被划分为"年轻一代"和"老一代".所有对象最初都分配在年轻一代(有时称为"托儿所").由于大多数对象都是短命的,因此GC更积极地从年轻一代中释放最近的垃圾.如果一个物体在年轻一代的收集周期中存活下来,它将被移动到旧一代(有时被称为"终身一代"),其处理频率较低.

所以,在我的头脑中,我会说"不,'最后'的modifer无助于GC减少其工作量".

在我看来,优化Java内存管理的最佳策略是尽快消除虚假引用.您可以在完成使用后立即将"null"指定给对象引用.

或者,更好的是,最小化每个声明范围的大小.例如,如果在1000行方法的开头声明一个对象,并且如果该对象保持活动直到该方法的作用域(最后一个结束大括号)结束,那么该对象可能会保持活动的时间长得多必要.

如果你使用只有十几行代码的小方法,那么在该方法中声明的对象将更快地超出范围,并且GC将能够以更高效的方式完成大部分工作.年轻一代.除非绝对必要,否则不希望将对象移动到旧代.

  • 不可能将null赋给已经创建的最终对象,那么也许*final*而不是帮助,可能会使事情变得更难 (2认同)

Aaron.. 37

声明局部变量final不会影响垃圾收集,它只表示您无法修改变量.上面的示例不应该编译,因为您正在修改totalWeight已标记的变量final.另一方面,声明一个原语(double而不是Double)final将允许将该变量内联到调用代码中,这样可能会导致一些内存和性能提升.当您public static final Strings在课堂上有多个时,可以使用此选项.

通常,编译器和运行时将优化它的位置.最好适当地编写代码,而不是试图太棘手.使用final时,你不希望变量进行修改做.假设编译器将执行任何简单的优化,如果您担心性能或内存使用,请使用分析器来确定真正的问题.


SCdF.. 25

不,这显然不是真的.

请记住,final这并不意味着不变,它只是意味着您无法更改参考.

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

可能存在一些小的优化,这些优化基于JVM永远不必修改引用的知识(例如没有检查它是否已经改变)但是它会很小而不用担心.

Final 应该被认为是开发人员有用的元数据而不是编译器优化.


小智.. 16

有些要点清楚:

  • 淘汰参考不应该帮助GC.如果是,则表明您的变量超出范围.对象裙带关系的例外是一个例外.

  • 在Java中还没有堆栈分配.

  • 声明变量final表示您不能(在正常条件下)为该变量赋值.由于final没有说明范围,因此它没有说明它对GC的影响.


benjismith.. 11

好吧,我不知道在这种情况下使用"最终"修饰符,或者它对GC的影响.

但我可以告诉你:你使用Boxed值而不是原语(例如,Double而不是double)将在堆上而不是堆栈上分配这些对象,并且会产生GC必须清理的不必要的垃圾.

我只在现有API需要时使用盒装基元,或者当我需要可空的灵长类动物时.


Thorbjørn Ra.. 5

初始赋值后不能更改最终变量(由编译器强制执行).

这不会改变垃圾收集的行为.唯一的问题是这些变量在不再使用时不能被置换(这可能有助于在内存紧张的情况下进行垃圾收集).

您应该知道final允许编译器对要优化的内容做出假设.内联代码,不包括已知无法访问的代码.

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

println将不包含在字节代码中.