Java 不可变对象如何帮助减少垃圾收集带来的开销?

Java 不可变对象如何帮助减少垃圾收集带来的开销?,java,multithreading,garbage-collection,Java,Multithreading,Garbage Collection,我是一个新手,我从前两个答案中读到了关于垃圾收集的内容 现在,与使用现有对象(在多线程应用程序中)相比,即使程序员必须创建新对象,也要使用不可变对象,这说明创建对象的成本可以通过垃圾收集减少的内存开销来弥补,以及消除代码以保护可变对象不受线程干扰和内存一致性错误的影响: 对象创建的影响通常被高估,并且可能会被忽略 被一些与不可变对象相关的效率所抵消。 其中包括由于垃圾收集而减少的开销,以及 消除保护可变对象免受攻击所需的代码 腐败 问题是怎么做?垃圾收集与对象的易变性或不可变性有什么关系?当对象

我是一个新手,我从前两个答案中读到了关于垃圾收集的内容

现在,与使用现有对象(在多线程应用程序中)相比,即使程序员必须创建新对象,也要使用不可变对象,这说明创建对象的成本可以通过垃圾收集减少的内存开销来弥补,以及消除代码以保护可变对象不受线程干扰和内存一致性错误的影响:

对象创建的影响通常被高估,并且可能会被忽略 被一些与不可变对象相关的效率所抵消。 其中包括由于垃圾收集而减少的开销,以及 消除保护可变对象免受攻击所需的代码 腐败


问题是怎么做?垃圾收集与对象的易变性或不可变性有什么关系?

当对象不可变时,有时分配的资源更少

简单例子

 Date getDate(){
   return copy(this.date);
 }
每次共享时,我都必须复制
日期
,因为它是可变的,否则调用者可能会对它进行变异。如果
getDate
被大量调用,分配率将显著增加,这将给
GC

另一方面,Java-8日期是不可变的

LocalDate getDate(){
  return this.date;
}
请注意,由于不变性,我不需要复制日期(分配一个新对象)(我很高兴与您共享该对象,因为我知道您不能对其进行变异)

现在,您可能会想,我如何将其应用于“有用”或复杂的数据结构,而不导致大量分配(由于防御副本),您完全正确,但有一种艺术称为
函数式编程
持久数据结构
(即:你会产生一种错觉,认为这是一份新的复制品,而实际上复制品与原件有很多相同之处)


大多数函数式语言(我所知道的所有函数式语言)都是垃圾收集的,这一点也不奇怪。

不可变对象不需要防御性副本,如果您在上下文中共享它们(例如调用您不信任的代码)这可能意味着不可变对象的读取在垃圾方面可能更低

另一方面,每次更改不可变对象时,无论是否需要,都必须创建新对象。在这方面,不可变对象可能会创建更多的垃圾

真正的问题是,根据使用情况,您是进行大量读取还是大量写入(或混合),不可变对象可以保存对象或创建更多对象,因此根据您的特定用例使用不可变或可变对象是有意义的

注:大多数情况下,正确性远比性能重要,虽然一般来说不可变对象的开销更大,但使用不可变对象证明数据模型的正确性要容易得多,而且仅为了清晰和易于推理,使用不可变对象是值得的。

中的Brian Goetz对此进行了解释基本上,它与垃圾收集器的工作方式有关。如果新对象引用旧对象,它所做的工作要比旧对象引用旧对象所做的工作要少

下面是链接文章的摘录和示例类:

public class MutableHolder {
  private Object value;
  public Object getValue() { return value; }
  public void setValue(Object o) { value = o; }
}

public class ImmutableHolder {
  private final Object value;
  public ImmutableHolder(Object o) { value = o; }
  public Object getValue() { return value; }
}
在大多数情况下,当holder对象更新为引用 不同的对象,新的引用对象是一个年轻的对象
MutableHolder
通过调用
setValue()
,我们创造了一种局面 一个较老的对象引用一个较年轻的对象。另一方面 创建一个新的
ImmutableHolder
对象,则会创建一个较年轻的对象 引用一个旧的

后一种情况是,大多数对象指向较旧的对象 对一代人的垃圾收集器要温和得多
MutableHolder
生活在老一代的人都是变异的,所有的 必须扫描卡上包含
可变持有人的对象
用于下一次小收藏中的从老到新的参考

对长寿命容器对象使用可变引用 增加了在集合中跟踪从旧引用到新引用所做的工作 时间

逃逸分析

考虑到在需要更改现有对象时实例化新对象会创建大量对象,最新JVM中的对象分配机制得到了很大改进

看一看(链接文章中也提到了)。许多对象不会在堆上分配(而是在堆栈上内联/分配),因此GC与它们无关(实际上GC根本不知道这些对象存在)


虽然转义分析机制不仅与不变性有关,但它可以在不可变的上下文中更有效地使用(例如,
Person
对象,在链接的Oracle文档中的方法调用过程中不会更改)。

现代GC对于“年轻死亡”的对象非常有效。不要害怕创建和使用寿命较短的对象;但一定要注意保持引用对象的时间较长(例如缓存)。问题实际上不是关于不变性,而是关于寿命。在您链接的教程中,确切的短语是“垃圾收集导致的开销减少”。没有“内存”一词你应该在问题中纠正它,因为不可变对象实际上可能会消耗更多内存(因为如果你想在现有对象中更改某些内容,你必须创建一个新对象),同时减少了GC的总体负担。下面我的答案中有更多细节。非常感谢。虽然最后两段以外的答案对我来说很清楚,但我无法理解最后两段。如果你有时间,你可以在这里解释一下,否则没问题。再次感谢你的回答。@Solace你的头发是黄色的吗