在Java中将final用于变量会改善垃圾回收吗? 您所在的位置:网站首页 参数用final修饰 在Java中将final用于变量会改善垃圾回收吗?

在Java中将final用于变量会改善垃圾回收吗?

2023-08-25 00:53| 来源: 网络整理| 查看: 265

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

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

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

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

这是真的?

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

12345public class MyClass {    public final MyOtherObject obj; }

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

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

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

同样,重要的是要认识到Java堆内存被划分为"年轻"和"旧"。所有对象最初都是在年轻一代中分配的(有时称为"托儿所")。由于大多数对象都是短命的,因此GC在从年轻一代释放最新垃圾方面更加积极。如果某个对象在年轻一代的收集周期中幸存下来,则将其移入旧一代(有时称为"终身一代"),该对象的处理频率较低。

因此,我想说的是:"不,'最终'修饰符无法帮助GC减少工作量"。

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

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

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

相关讨论 值得深思。我一直以为内联代码会更快,但是如果jvm内存不足,它也会变慢。嗯... 我只是在这里猜测...但是我假设JIT编译器可以内联最终的原始值(而不是对象),以适度提高性能。另一方面,代码内联能够产生重大的优化,但与最终变量无关。 不可能将null分配给已经创建的最终对象,那么也许最终而不是帮助可能会使事情变得更困难 也可以使用{}来限制大型方法的作用域,而不是将其划分为可能与其他类方法不相关的几个私有方法。 您还可以在可能的情况下使用局部变量而不是字段,以增强内存垃圾收集并减少引用关系。

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

通常,编译器和运行时将在可能的地方进行优化。最好适当地编写代码,而不要太棘手。当您不想修改变量时,请使用final。假定编译器将执行任何简单的优化,并且如果您担心性能或内存使用,请使用探查器确定实际问题。

不,这很不正确。

请记住,final并不表示常量,而只是表示您不能更改引用。

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

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

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

需要清除的几点:

淘汰参考文献对GC无效。如果确实如此,则表明您的变量超出了范围。一种例外是对象裙带关系。

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

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

相关讨论 Java中有栈上分配(对原语和对堆中对象的引用)的分配:stackoverflow.com/a/8061692/32453 Java要求与匿名类/ lambda处于同一闭包中的对象也必须是最终对象,但是仅仅是为了减少"所需的内存/帧" /减少混乱,所以与其被收集无关...

好吧,我不知道在这种情况下使用"最终"修饰符或其对GC的影响。

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

仅在现有API要求或需要可为空的基元时,才使用盒装基元。

相关讨论 你是对的。我只需要一个简单的例子来解释我的问题。

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

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

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

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

println将不包含在字节码中。

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

世代垃圾收集器的情况鲜为人知。 (有关简要说明,请阅读Benjismith的答案,以更深入地了解本文末尾的文章)。

世代GC的想法是,大多数时候只需要考虑年轻一代。扫描根位置以获取参考,然后扫描年轻代对象。在此更频繁的扫描期间,不会检查旧一代中的任何对象。

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

旧对象不能引用较年轻的对象的原因是,由于未在次要集合中检查该旧对象,因此,如果仅对对象的引用保留在旧对象中,则该对象将不会被标记,并且会错误地进行处理。在清扫阶段被释放。

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

文章:

IBM的垃圾回收:历史,热点JVM和性能。这些可能不再完全有效,因为它可以追溯到2003/04年,但是它们提供了一些易于理解的GC见解。

太阳在调整垃圾收集

GC对无法访问的引用起作用。这与"最终"无关,后者仅是一次性分配的主张。某些VM的GC是否可以使用"最终"?我不知道如何或为什么。

似乎有很多答案在猜测中。 事实是,在字节码级别上没有用于局部变量的最终修饰符。 虚拟机永远不会知道您的局部变量是否定义为final。

您的问题的答案很明确。

相关讨论 可能是这样,但是编译器仍可以在数据流分析期间使用最终信息。 @WernerVanBelle编译器已经知道一个变量仅设置一次。它必须已经进行了数据流分析,以便知道变量可能为null,在使用前未初始化等。因此,局部最终不向编译器提供任何新信息。 它没有。数据流分析可以推断出很多东西,但是有可能在一个块内有一个图灵完整的程序,该程序将设置或不设置局部变量。编译器无法事先知道该变量是否将被写入并将完全为常量。因此,没有final关键字的编译器无法保证变量是否为final。 @WernerVanBelle Im真的很感兴趣,您能举个例子吗?我看不到如何对编译器不知道的非最终变量进行最终赋值。编译器知道,如果您有未初始化的变量,则不会使用它。如果尝试在循环内分配最终变量,则编译器不会允许您。在一个可以声明为final的变量但不能声明为final的变量的示例中,编译器不能保证它是final的?我怀疑任何示例都会是一开始就无法声明为final的变量。 重新阅读您的注释后,我看到您的示例是在条件块中初始化的变量。除非所有条件路径都一次初始化变量,否则这些变量首先不能为final。因此,编译器确实知道这种声明-这就是它如何允许您编译在两个不同位置初始化的最终变量(请考虑final int x; if (cond) x=1; else x=2;)。因此,没有final关键字的编译器可以保证变量是否为final。 我想说的是,没有最终的编译器就不知道变量实际上是常量(尽管它可以是常量)。对于final,可以肯定的是。这意味着final允许比没有允许更多的优化。 嗯,你得举个例子。我仍然可以确定编译器是否知道关键字是否为常数。想象一下:编译器"假装"每个局部变量都声明为final。如果可以使用final变量进行编译,则现在"知道"没有关键字就可以是final。如果无法编译,则相反地"知道"该变量不能为最终变量。是否有人在文本中添加关键字对此没有影响。 如果变量是常量,但编译器将无法检测到它。 int a = 1;而(a 图2中完整的示例为:{int a = 3;如果(complexFalse())a = 4; }其中complexFalse()是编译器无法证明的方法,将始终为false(请参阅halting-problem)。在这种情况下,如果不将if语句转换为三元表达式,就无法声明最终值,此时数据流仍然可以正常工作。 IOW虽然微不足道是正确的,但在实践中没有用。 @Recurse那个例子是无效的;您的示例中的a不能声明为final。 {final int a = 3; if (complexFalse()) a = 4}不会在Java中编译,因此包含或缺少final关键字不适用。关键是,人类用final修饰符(在Java中)无法做任何事情,它将告诉编译器它尚不知道的任何内容。

默认情况下,所有方法和变量都可以在子类中被覆盖。如果要保存子类中的子类,可以使用关键字final将它们声明为final。 例如 final int a=10; final void display(){......} 将方法设为final可以确保超类中定义的功能永远不会更改。同样,最终变量的值永远不能更改。最终变量的行为类似于类变量。

我唯一喜欢将局部变量声明为final的情况是:

我必须将它们定型,以便可以与某些匿名类共享(例如:创建守护程序线程并让其从封闭方法访问某些值)

我想将它们定为最终值(例如:不应/不会被错误覆盖的某些值)

Does they help in fast garbage collection? AFAIK a object becomes a candidate of GC collection if it has zero strong references to it and in that case as well there is no guarantee that they will be immediately garbage collected . In general, a strong reference is said to die when it goes out of scope or user explicitly reassign it to null reference, thus, declaring them final means that reference will continue to exists till the method exists (unless its scope is explicitly narrowed down to a specific inner block {}) because you can't reassign final variables. So I think w.r.t Garbage Collection 'final' may introduce a unwanted possible delay.

blockquote> blockquote>

绝对,只要使对象的寿命变短,这会带来内存管理的巨大好处,最近,我们检查了在一个测试中具有实例变量而在另一方法中具有方法级局部变量的导出功能。在负载测试期间,JVM在第一次测试时抛出了内存不足错误,并且JVM被暂停。但在第二次测试中,由于更好的内存管理,成功获得了报告。

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有