在Java中使用finalvariables可以改善垃圾回收吗?

今天,我和我的同事讨论了Java中final关键字的用法,以改善垃圾收集。

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

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

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

这是真的?

这里有一个稍微不同的例子,其中一个是最终的引用types字段,而不是最终的值types局部variables:

 public class MyClass { public final MyOtherObject obj; } 

每次创buildMyClass的实例时,都将创build对MyOtherObject实例的传出引用,并且GC必须遵循该链接才能查找活动对象。

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

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

另外,认识到Java堆内存被分成“年轻一代”和“旧一代”是很重要的。 所有对象最初都分配在年轻一代(有时被称为“苗圃”)。 由于大多数物品都是短命的,所以GC更加积极地解放年轻一代的近期垃圾。 如果一个物体在年轻一代的收集周期中存活下来,它就会被移到老一代(有时被称为“老一代”),而这个老一代处理得不那么频繁。

所以,从我的头顶上来说,我要说“不,最终”的改动并不能帮助GC减less工作量。

在我看来,在Java中优化内存pipe理的最佳策略是尽可能快地消除虚假引用。 一旦完成使用,您可以通过将“null”分配给对象引用来完成此操作。

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

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

声明一个局部variablesfinal不会影响垃圾收集,这只意味着你不能修改这个variables。 你上面的例子不应该编译,因为你正在修改已标记为final的variablestotalWeight 。 另一方面,声明一个原语( double而不是Doublefinal将允许将该variables内联到调用代码中,这样可能会导致一些内存和性能的提高。 当你在一个类中有许多public static final Strings时使用这个。

一般来说,编译器和运行时会优化它的位置。 最好是编写适当的代码,而不要过于棘手。 当你不希望variables被修改时使用final 。 假设编译器将执行任何简单的优化,如果您担心性能或内存使用,请使用分析器来确定真正的问题。

不,它强调不是真的。

记住, final并不意味着不变,只是意味着你不能改变参考。

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

可能会有一些小的优化,基于JVM永远不必修改引用的知识(比如没有检查它是否已经改变),但是它是如此小以至于不用担心。

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

有些要清理的地方:

  • 取消引用不应该帮助GC。 如果是的话,这将表明你的variables超出了范围。 目标裙带关系是一个例外。

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

  • 声明一个variablesfinal意味着你不能(在正常情况下)给该variables赋一个新的值。 由于最终没有提到范围,所以没有提到它对GC的影响。

那么,我不知道在这种情况下使用“final”修饰符,或者它对GC的影响。

但是我可以告诉你:使用Boxed值而不是原始数据(例如Double而不是double)会将这些对象分配到堆而不是堆栈中,并且会产生GC将不得不清除的不必要的垃圾。

我只在现有的API需要时才使用盒装原语,或者当我需要可空的primatives时。

最终variables在初始赋值之后不能被更改(由编译器强制执行)。

这不会改变垃圾收集的行为。 唯一的问题是这些variables在不再被使用时不能被清零(这可能有助于垃圾回收在内存紧张的情况下)。

你应该知道final允许编译器对优化什么做出假设。 内联代码,不包括已知不可访问的代码。

 final boolean debug = false; ...... if (debug) { System.out.println("DEBUG INFO!"); } 

println将不会包含在字节码中。

GC作用于不可达的参考。 这与“最后”无关,这只是一次性分配的主张。 有些虚拟机的GC可以使用“final”吗? 我不知道如何或为什么。

有一个不太知名的世代垃圾收集器的angular落案例。 (对于一个简要的描述阅读benjismith的答案更深入的洞察阅读最后的文章)。

分代GC的想法是大多数时候只有年轻一代需要考虑。 对根位置进行参考扫描,然后扫描年轻代对象。 在更频繁的扫描期间,不检查旧一代中的对象。

现在,问题来自一个事实,一个对象不允许引用年轻的对象。 当一个长寿命的(旧一代)对象获取到一个新对象的引用时,该引用必须由垃圾收集器显式跟踪(请参阅热点JVM收集器上的IBM文章),实际上会影响GC性能。

旧对象之所以不能引用年轻对象,是因为旧对象没有在次要集合中被检查,如果对象的唯一引用被保存在旧对象中,则它将不被标记,并且将被错误地在扫描阶段释放。

当然,正如很多人指出的那样,final关键字并不会影响垃圾收集器,但是它确实保证了如果这个对象在次要的集合中生存下来并且把它放到老的堆中,这个引用永远不会变成一个年轻的对象。

文章:

IBM对垃圾收集: 历史 ,在热点JVM和性能 。 这些可能不再是完全有效的,因为它可以追溯到2003/04年度,但是它们给出了一些容易理解的地理信息系统。

太阳在调整垃圾收集

final对本地variables和参数的生成没有影响,所以不会影响运行时的性能。 如果一个类没有子类,那么HotSpot将该类看作是最终的(如果一个类中断了这个假设,它将被加载)。 我相信final的方法和类很相似。 在静态字段上的final可以允许variables被解释为一个“编译时常量”,并在此基础上由javac完成优化。 final的字段允许JVM有一些自由来忽略发生在关系之前

似乎有很多答案是stream浪的猜测。 事实是, 在字节码级别没有局部variables的最终修饰符。 虚拟机永远不会知道你的局部variables是否被定义为final。

你的问题的答案是强调没有。

所有的方法和variables都可以在子类中被默认覆盖。如果我们想保存子类覆盖超类的成员,我们可以使用关键字final声明它们为final。 eg- final int a=10; final void display(){......}使方法最终确保在超类中定义的function永远不会改变。 类似地,最终variables的值也不能改变。 最终variables的行为类似于类variables。

我能想到的唯一的事情是编译器可能会优化掉最后的variables,并将它们作为常量embedded到代码中,因此最终没有分配内存。

绝对地说,只要使对象的生命更短,产生很大的内存pipe理效益,最近我们考察了具有实例variables的一个testing和另一个具有方法级别局部variables的testing的输出function。 在负载testing期间,JVM在第一次testing中抛出内存错误,JVM停止。 但在第二次testing中,由于更好的内存pipe理,成功获得报告。

我唯一喜欢把局部variables声明为final的时候是:

  • 必须使它们成为最终的,以便它们可以与一些匿名类共享(例如:创build守护进程线程并让它从封闭方法访问一些值)

  • 想让它们成为最终的(例如:某些不应该/不会被错误覆盖的值)

他们帮助快速垃圾收集?
AFAIK一个对象成为GC收集的候选者,如果它没有强引用它,那么也不能保证它们会立即被垃圾收集。 一般来说,当一个强大的引用超出范围或者用户明确地将其重新分配给空引用时,就会死掉,因此,声明它们最终意味着引用将继续存在直到该方法存在(除非其范围被明确地缩小到一个特定的内部块{}),因为你不能重新分配最终variables。 所以我认为垃圾收集“最终” 可能会引起不必要的可能延迟。