不可变对象如何帮助减少因垃圾收集而产生的开销?

Sol*_*ace 21 java multithreading garbage-collection

我对于新手,我已经从第一两个答案阅读有关垃圾收集在这里.

现在证明使用不可变对象,即使程序员必须创建新对象,与使用现有对象(在多线程应用程序中)相比,本教程说,对象创建的成本是由内存开销减少弥补的.由于垃圾收集,并且消除了保护可变对象免受线程干扰和内存一致性错误的代码:

对象创建的影响经常被高估,并且可以通过与不可变对象相关联的一些效率来抵消.这些包括由于垃圾收集而减少的开销,以及消除保护可变对象免于损坏所需的代码.

问题是如何?垃圾收集与物体的可变性或不变性有什么关系?

Sle*_*idi 15

有时在对象不可变时分配较少.

简单的例子

 Date getDate(){
   return copy(this.date);
 }
Run Code Online (Sandbox Code Playgroud)

Date每次分享它都要复制,因为它是可变的,或者调用者能够改变它.如果getDate被大量召唤,分配率将急剧增加,这将给这个问题带来压力GC

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

LocalDate getDate(){
  return this.date;
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于不变性,我不需要复制日期(分配新对象)(我很乐意与您分享对象,因为我知道您不能改变它).

现在你可能认为我怎么能应用此"有用"或复杂的数据结构,而不会造成大规模的分配(由于防守副本),你是绝对正确的,但有一个叫做艺术functional programmingpersistent data structures(即:它是一个你的错觉新副本实际上副本与原始副本共享很多).

人们不应该对大多数函数式语言(我所知道的所有语言)都是垃圾收集感到惊讶.

  • @Solace One(非常简单,经典)的例子是一个列表:当你有一个不可变的列表,例如列表`[a,b,c,d,e]`,那么你可以实现这个(不可变的!)数据结构的方式允许你从这个列表中"删除"前两个元素 - 但这不会**创建​​一个新列表或真正修改现有列表.相反,您将获得由与第一个存储器相同的内存支持的"子列表"`[c,d,e]`.另请参阅https://en.wikipedia.org/wiki/Persistent_data_structure和相关的stackoverflow问题. (3认同)

Pet*_*rey 9

如果您要跨越上下文(.eg调用您不信任的代码)或线程安全共享它们,则不可变对象不需要防御性副本.这可能意味着不可变对象的读取在垃圾方面可能更低.

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

真正的问题是你是在进行大量的读取还是大量的写​​入(或混合)取决于使用不可变对象可以保存对象或创建更多对象,因此根据您的特定用途使用Immutable或Mutable对象是有意义的案件.

注意:大多数情况下,正确性远远超过性能,而且一般来说,不可变对象具有更高的开销IMHO,使用Immutable对象更容易证明数据模型的正确性,值得使用不可变对象为了清晰和易于推理.

  • 由于这个例子,我不得不将另一个答案标记为已被接受,但我很感激这个建议.这对我很有价值.非常感谢你. (2认同)

Dra*_*vic 7

这篇文章中, 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; }
}
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,当更新持有者对象以引用不同的对象时,新的指示对象是年轻的对象.如果我们MutableHolder通过调用更新a setValue(),我们创建了一个旧对象引用较年轻对象的情况.另一方面,通过创建新ImmutableHolder对象,较年轻的对象引用较旧的对象.

后一种情况,大多数物体指向较旧的物体,对于世代垃圾收集器来说要温和得多.如果 MutableHolder生活在旧一代中的所有对象发生变异,则MutableHolder必须在下一个次要集合中扫描包含该对象的卡上的所有对象以查找旧到年份的引用.

对长寿命容器对象使用可变引用增加了在收集时跟踪旧到年份引用所做的工作.

逃生分析

关于因为在需要更改现有对象时实例化新对象而导致创建大量对象的问题,在最新的JVM中对象分配机制得到了很大改进.

看一下逃逸分析(在链接文章中也有提到).许多对象不会在堆上分配(但在堆栈上内联/分配),因此GC与它们无关(实际上GC并不知道这些对象存在).

尽管不仅仅与不可变性相关,但是可以在不可变上下文中更有效地利用转义分析机制(例如Person,在链接的Oracle文档中的方法调用期间不会更改转义的对象).