今天,我和我的同事讨论了final
Java中关键字的用法,以改进垃圾收集.
例如,如果您编写如下方法:
public Double doCalc(final Double value) { final Double maxWeight = 1000.0; final Double totalWeight = maxWeight * value; return totalWeight; }
声明方法中的变量final
将有助于垃圾收集在方法退出后从方法中未使用的变量清除内存.
这是真的?
这是一个稍微不同的示例,一个具有最终引用类型字段,而不是最终值类型局部变量:
public class MyClass { public final MyOtherObject obj; }
每次创建MyClass实例时,您都将创建对MyOtherObject实例的传出引用,并且GC必须按照该链接查找实时对象.
JVM使用标记扫描GC算法,该算法必须检查GC"根"位置中的所有实时引用(如当前调用堆栈中的所有对象).每个活动对象被"标记"为活着,并且活动对象引用的任何对象也被标记为活着.
完成标记阶段后,GC扫描堆,释放所有未标记对象的内存(并为剩余的活动对象压缩内存).
此外,重要的是要认识到Java堆内存被划分为"年轻一代"和"老一代".所有对象最初都分配在年轻一代(有时称为"托儿所").由于大多数对象都是短命的,因此GC更积极地从年轻一代中释放最近的垃圾.如果一个物体在年轻一代的收集周期中存活下来,它将被移动到旧一代(有时被称为"终身一代"),其处理频率较低.
所以,在我的头脑中,我会说"不,'最后'的modifer无助于GC减少其工作量".
在我看来,优化Java内存管理的最佳策略是尽快消除虚假引用.您可以在完成使用后立即将"null"指定给对象引用.
或者,更好的是,最小化每个声明范围的大小.例如,如果在1000行方法的开头声明一个对象,并且如果该对象保持活动直到该方法的作用域(最后一个结束大括号)结束,那么该对象可能会保持活动的时间长得多必要.
如果你使用只有十几行代码的小方法,那么在该方法中声明的对象将更快地超出范围,并且GC将能够以更高效的方式完成大部分工作.年轻一代.除非绝对必要,否则不希望将对象移动到旧代.
声明局部变量final
不会影响垃圾收集,它只表示您无法修改变量.上面的示例不应该编译,因为您正在修改totalWeight
已标记的变量final
.另一方面,声明一个原语(double
而不是Double
)final
将允许将该变量内联到调用代码中,这样可能会导致一些内存和性能提升.当您public static final Strings
在课堂上有多个时,可以使用此选项.
通常,编译器和运行时将优化它的位置.最好适当地编写代码,而不是试图太棘手.使用final
时,你不希望变量进行修改做.假设编译器将执行任何简单的优化,如果您担心性能或内存使用,请使用分析器来确定真正的问题.
不,这显然不是真的.
请记住,final
这并不意味着不变,它只是意味着您无法更改参考.
final MyObject o = new MyObject(); o.setValue("foo"); // Works just fine o = new MyObject(); // Doesn't work.
可能存在一些小的优化,这些优化基于JVM永远不必修改引用的知识(例如没有检查它是否已经改变)但是它会很小而不用担心.
Final
应该被认为是开发人员有用的元数据而不是编译器优化.
有些要点清楚:
淘汰参考不应该帮助GC.如果是,则表明您的变量超出范围.对象裙带关系的例外是一个例外.
在Java中还没有堆栈分配.
声明变量final表示您不能(在正常条件下)为该变量赋值.由于final没有说明范围,因此它没有说明它对GC的影响.
好吧,我不知道在这种情况下使用"最终"修饰符,或者它对GC的影响.
但我可以告诉你:你使用Boxed值而不是原语(例如,Double而不是double)将在堆上而不是堆栈上分配这些对象,并且会产生GC必须清理的不必要的垃圾.
我只在现有API需要时使用盒装基元,或者当我需要可空的灵长类动物时.
初始赋值后不能更改最终变量(由编译器强制执行).
这不会改变垃圾收集的行为.唯一的问题是这些变量在不再使用时不能被置换(这可能有助于在内存紧张的情况下进行垃圾收集).
您应该知道final允许编译器对要优化的内容做出假设.内联代码,不包括已知无法访问的代码.
final boolean debug = false; ...... if (debug) { System.out.println("DEBUG INFO!"); }
println将不包含在字节代码中.