在 Java 中使用 final for 变量是否会改善垃圾回收?

2022-08-31 11:20:29

今天,我和我的同事们讨论了在Java中使用关键字来改进垃圾回收。final

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

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

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

这是真的吗?


答案 1

下面是一个略有不同的示例,其中包含最终的引用类型字段,而不是最终的值类型的局部变量:

public class MyClass {

   public final MyOtherObject obj;

}

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

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

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

此外,重要的是要认识到Java堆内存被划分为“年轻一代”和“老一代”。所有对象最初都分配给年轻一代(有时称为“托儿所”)。由于大多数对象都是短暂的,因此GC在从年轻一代中释放最近的垃圾方面更加积极。如果一个对象在年轻一代的收集周期中幸存下来,它就会被转移到老一代(有时称为“终身一代”),其处理频率较低。

所以,在我的头顶上,我要说“不,'最终'修饰符无助于GC减少其工作量”。

在我看来,在Java中优化内存管理的最佳策略是尽快消除虚假引用。您可以通过在使用完对象引用后立即将其“null”分配给它来做到这一点。

或者,更好的是,最小化每个声明范围的大小。例如,如果在 1000 行方法的开头声明一个对象,并且该对象在该方法的作用域(最后一个右大括号)结束之前保持活动状态,则该对象可能会保持活动状态,这实际上需要的时间要长得多。

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


答案 2

声明局部变量不会影响垃圾回收,它只是意味着您无法修改该变量。上面的示例不应编译,因为您正在修改已标记为 的变量。另一方面,声明基元(而不是)将允许将该变量内联到调用代码中,因此这可能会导致一些内存和性能改进。当您在类中有多个时,将使用此选项。finaltotalWeightfinaldoubleDoublefinalpublic static final Strings

通常,编译器和运行时将尽可能优化。最好适当地编写代码,不要试图太棘手。在不希望修改变量时使用。假设编译器将执行任何简单的优化,如果您担心性能或内存使用,请使用探查器来确定真正的问题。final