在 Java 中对变量使用 final 会改善垃圾收集吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/306862/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 12:56:10  来源:igfitidea点击:

Does using final for variables in Java improve garbage collection?

javagarbage-collectionfinal

提问by Goran Martinic

Today my colleagues and me have a discussion about the usage of the finalkeyword in Java to improve the garbage collection.

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

For example, if you write a method like:

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

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

Declaring the variables in the method finalwould help the garbage collection to clean up the memory from the unused variables in the method after the method exits.

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

Is this true?

这是真的?

采纳答案by benjismith

Here's a slightly different example, one with final reference-type fields rather than final value-type local variables:

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

public class MyClass {

   public final MyOtherObject obj;

}

Every time you create an instance of MyClass, you'll be creating an outgoing reference to a MyOtherObject instance, and the GC will have to follow that link to look for live objects.

每次创建 MyClass 的实例时,都会创建一个对 MyOtherObject 实例的传出引用,并且 GC 必须遵循该链接来查找活动对象。

The JVM uses a mark-sweep GC algorithm, which has to examine all the live refereces in the GC "root" locations (like all the objects in the current call stack). Each live object is "marked" as being alive, and any object referred to by a live object is also marked as being alive.

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

After the completion of the mark phase, the GC sweeps through the heap, freeing memory for all unmarked objects (and compacting the memory for the remaining live objects).

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

Also, it's important to recognize that the Java heap memory is partitioned into a "young generation" and an "old generation". All objects are initially allocated in the young generation (sometimes referred to as "the nursery"). Since most objects are short-lived, the GC is more aggressive about freeing recent garbage from the young generation. If an object survives a collection cycle of the young generation, it gets moved into the old generation (sometimes referred to as the "tenured generation"), which is processed less frequently.

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

So, off the top of my head, I'm going to say "no, the 'final' modifer doesn't help the GC reduce its workload".

所以,在我的脑海里,我会说“不,'最终'修饰符不会帮助 GC 减少它的工作量”。

In my opinion, the best strategy for optimizing your memory-management in Java is to eliminate spurious references as quickly as possible. You could do that by assigning "null" to an object reference as soon as you're done using it.

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

Or, better yet, minimize the size of each declaration scope. For example, if you declare an object at the beginning of a 1000-line method, and if the object stays alive until the close of that method's scope (the last closing curly brace), then the object might stay alive for much longer that actually necessary.

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

If you use small methods, with only a dozen or so lines of code, then the objects declared within that method will fall out of scope more quickly, and the GC will be able to do most of its work within the much-more-efficient young generation. You don't want objects being moved into the older generation unless absolutely necessary.

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

回答by benjismith

Well, I don't know about the use of the "final" modifier in this case, or its effect on the GC.

好吧,我不知道在这种情况下使用“final”修饰符,也不知道它对 GC 的影响。

But I cantell you this: your use of Boxed values rather than primitives (e.g., Double instead of double) will allocate those objects on the heap rather than the stack, and will produce unnecessary garbage that the GC will have to clean up.

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

I only use boxed primitives when required by an existing API, or when I need nullable primatives.

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

回答by Sijin

The only thing that I can think of is that the compiler might optimize away the final variables and inline them as constants into the code, thus you end up with no memory allocated.

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

回答by SCdF

No, it is emphatically not true.

不,这绝对不是真的。

Remember that finaldoes not mean constant, it just means you can't change the reference.

请记住,final这并不意味着常量,它只是意味着您不能更改引用。

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

There may be some small optimisation based around the knowledge that the JVM will never have to modify the reference (such as not having check to see if it has changed) but it would be so minor as to not worry about.

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

Finalshould be thought of as useful meta-data to the developer and not as a compiler optimisation.

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

回答by Aaron

Declaring a local variable finalwill not affect garbage collection, it only means you can not modify the variable. Your example above should not compile as you are modifying the variable totalWeightwhich has been marked final. On the other hand, declaring a primitive (doubleinstead of Double) finalwill allows that variable to be inlined into the calling code, so that could cause some memory and performance improvement. This is used when you have a number of public static final Stringsin a class.

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

In general, the compiler and runtime will optimize where it can. It is best to write the code appropriately and not try to be too tricky. Use finalwhen you do not want the variable to be modified. Assume that any easy optimizations will be performed by the compiler, and if you are worried about performance or memory use, use a profiler to determine the real problem.

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

回答by dongilmore

GC acts on unreachable refs. This has nothing to do with "final", which is merely an assertion of one-time assignment. Is it possible that some VM's GC can make use of "final"? I don't see how or why.

GC 作用于无法访问的引用。这与“final”无关,它只是一次性赋值的断言。是否有可能某些 VM 的 GC 可以使用“final”?我不明白如何或为什么。

回答by David Rodríguez - dribeas

There is a not so well known corner case with generational garbage collectors. (For a brief description read the answer by benjismithfor a deeper insight read the articles at the end).

分代垃圾收集器有一个不太为人所知的极端情况。(有关简要说明,请阅读本吉史密斯的回答,以深入了解阅读末尾的文章)。

The idea in generational GCs is that most of the time only young generations need to be considered. The root location is scanned for references, and then the young generation objects are scanned. During this more frequent sweeps no object in the old generation are checked.

分代 GC 的想法是大多数时候只需要考虑年轻代。扫描根位置的引用,然后扫描年轻代对象。在这个更频繁的扫描期间,没有检查老年代中的对象。

Now, the problem comes from the fact that an object is not allowed to have references to younger objects. When a long lived (old generation) object gets a reference to a new object, that reference must be explicitly tracked by the garbage collector (see article from IBM on the hotspot JVM collector), actually affecting the GC performance.

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

The reason why an old object cannot refer to a younger one is that, as the old object is not checked in minor collections, if the only reference to the object is kept in the old object, it will not get marked, and would be wrongly deallocated during the sweep stage.

旧对象不能引用新对象的原因是,由于在小集合中没有检查旧对象,如果对对象的唯一引用保留在旧对象中,则不会被标记,并且会错误在扫描阶段解除分配。

Of course, as pointed by many, the final keyword does not reallly affect the garbage collector, but it does guarantee that the reference will never be changed into a younger object if this object survives the minor collections and makes it to the older heap.

当然,正如许多人指出的那样,final 关键字并不会真正影响垃圾收集器,但它确实保证如果该对象在次要收集中幸存并进入较旧的堆,则该引用永远不会更改为较年轻的对象。

Articles:

文章:

IBM on garbage collection: history, in the hotspot JVMand performance. These may no longer be fully valid, as it dates back in 2003/04, but they give some easy to read insight into GCs.

IBM 关于垃圾收集:历史热点 JVM性能。这些可能不再完全有效,因为它可以追溯到 2003/04 年,但它们提供了一些易于阅读的 GC 洞察。

Sun on Tuning garbage collection

Sun on Tuning 垃圾收集

回答by Tom Hawtin - tackline

finalon local variables and parameters makes no difference to the class files produced, so cannot affect runtime performance. If a class has no subclasses, HotSpot treats that class as if it is final anyway (it can undo later if a class that breaks that assumption is loaded). I believe finalon methods is much the same as classes. finalon static field may allow the variable to be interpreted as a "compile-time constant" and optimisation to be done by javac on that basis. finalon fields allows the JVM some freedom to ignore happens-beforerelations.

final局部变量和参数对生成的类文件没有影响,因此不会影响运行时性能。如果一个类没有子类,HotSpot 会将该类视为最终类(如果加载了破坏该假设的类,它可以稍后撤消)。我相信final方法与类非常相似。final在静态字段上可能允许将变量解释为“编译时常量”,并在此基础上由 javac 进行优化。finalon 字段允许 JVM 有一些自由来忽略发生之前的关系。

回答by Thorbj?rn Ravn Andersen

Final variables cannot be changed after initial assignment (enforced by the compiler).

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

This does not change the behaviour of the garbage collectionas such. Only thing is that these variables cannot be nulled when not being used any more (which may help the garbage collection in memory tight situations).

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

You should know that final allows the compiler to make assumptions about what to optimize. Inlining code and not including code known not to be reachable.

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

final boolean debug = false;

......

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

The println will not be included in the byte code.

println 不会包含在字节码中。

回答by Kirk

Some points to clear up:

需要澄清的几点:

  • Nulling out reference should not help GC. If it did, it would indicate that your variables are over scoped. One exception is the case of object nepotism.

  • There is no on-stack allocation as of yet in Java.

  • Declaring a variable final means you can't (under normal conditions) assign a new value to that variable. Since final says nothing about scope, it doesn't say anything about it's effect on GC.

  • 取消引用不应该对 GC 有帮助。如果是这样,则表明您的变量范围过大。一种例外情况是客体裙带关系。

  • 到目前为止,Java 中还没有堆栈上分配。

  • 声明一个变量 final 意味着您不能(在正常情况下)为该变量分配一个新值。由于 final 没有说明作用域,因此它没有说明它对 GC 的影响。