在 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
Does using final for variables in Java improve garbage collection?
提问by Goran Martinic
Today my colleagues and me have a discussion about the usage of the final
keyword 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 final
would 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 final
does 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 永远不必修改引用的知识的小优化(例如不必检查它是否已更改),但它是如此小以至于不必担心。
Final
should be thought of as useful meta-data to the developer and not as a compiler optimisation.
Final
应该被视为对开发人员有用的元数据,而不是编译器优化。
回答by Aaron
Declaring a local variable final
will 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 totalWeight
which has been marked final
. On the other hand, declaring a primitive (double
instead of Double
) final
will 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 Strings
in a class.
声明一个局部变量final
不会影响垃圾回收,它只是意味着你不能修改这个变量。当您修改totalWeight
已标记为的变量时,您上面的示例不应编译final
。另一方面,声明一个原语(double
而不是Double
)final
将允许将该变量内联到调用代码中,这样可能会导致一些内存和性能改进。当您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 final
when 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
final
on 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 final
on methods is much the same as classes. final
on static field may allow the variable to be interpreted as a "compile-time constant" and optimisation to be done by javac on that basis. final
on fields allows the JVM some freedom to ignore happens-beforerelations.
final
局部变量和参数对生成的类文件没有影响,因此不会影响运行时性能。如果一个类没有子类,HotSpot 会将该类视为最终类(如果加载了破坏该假设的类,它可以稍后撤消)。我相信final
方法与类非常相似。final
在静态字段上可能允许将变量解释为“编译时常量”,并在此基础上由 javac 进行优化。final
on 字段允许 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 的影响。