使用不可变对象的程序的性能

gra*_*eqc 4 java performance garbage-collection immutability

使用不可变对象编写程序会导致性能问题吗?如果给定的对象是不可变的,并且我们需要以某种方式更改其状态,则必须将其映射到状态稍有更改的新对象。因此,我们会发现自己处于这样一种情况:创建了很多对象,这些对象堵塞了内存,并且据我了解,这可能会给垃圾收集器带来问题。我所描述的情况是否正在发生,或者是否存在我不知道的关于该主题的某些方面?

Hol*_*ger 5

当您重复修改可变对象时,它可能会比重复构造新的不可变对象来表示中间状态产生更少的垃圾。但有几个原因可以解释为什么使用不可变对象仍然不一定会带来性能问题:

\n
    \n
  • 在典型的应用程序中,与不可变对象不会受到影响甚至获胜的其他用途相比,这种情况只是偶尔发生。最为显着地:

    \n
      \n
    • 返回不可变对象时,getter 不需要创建防御性副本
    • \n
    • 如果不可变,设置器可以通过引用存储传入参数
    • \n
    • 验证方法可以通过简单(或 na\xc3\xafve)方式实现,而不必处理先检查后执行的问题,因为不可变对象在检查和后续使用之间不能更改
    • \n
    • 不可变对象可以安全地共享,以实际减少创建的对象或使用的内存量
    • \n
    \n
  • \n
  • 垃圾收集对性能的影响常常被高估。我们通常使用不可变类型java.lang.String,受益于上述优点。字符串也是最常用的哈希映射键之一。

    \n

    StringBuilder\xe2\x80\x99s 是用于重复字符串操作场景的可变伴生类,但其主要优点在于分配的对象数量。字符串构造的问题在于每个对象都必须创建所包含字符的副本,因此在每一步构造新字符串时,重复连接字符等操作会导致二次时间复杂度。这StringBuilder在后台重新分配其缓冲区,但会进行非线性增长,从而为重复串联产生摊销线性时间复杂度。

    \n

    正如这个答案中所解释的,垃圾收集的成本主要取决于仍然存在的对象,因此临时对象通常不会对垃圾收集产生太大影响,除非您创建了过量的对象。但即使是后一种情况,也只有在您遇到实际性能问题并且公正的分析工具证明特定分配站点确实是罪魁祸首时才应解决。

    \n
  • \n
  • 复杂的应用程序可能必须处理撤消/重做或其他版本控制功能,无论如何都需要保留替代应用程序状态的副本。此时,使用不可变对象实际上可能成为一种优势,因为您不需要复制在应用程序状态的两个版本之间未更改的对象。更改后的应用程序状态可能包含 99% 与先前状态相同的对象。

    \n
  • \n
  • 该主题再次流行的重要原因之一是不可变对象提供了实现高效且正确的并行处理的最简单方法。无需获取锁来防止不一致的修改。如上所述,\xe2\x80\x99s 无需担心先检查后执行的问题。此外,当应用程序状态可以表示为对复合不可变对象的单个引用时,对并发更新的检查减少为简单的引用比较。也与这个答案进行比较。

    \n
  • \n
\n

因此,不可变对象有很多性能优势,可以弥补缺点(如果有的话)。同时,可以通过临时处理操作的可变对象同时保持整体不可变行为的既定解决方案来修复潜在的瓶颈。

\n

  • 那么,您可以使用任何操作来减慢应用程序的速度,只需重复执行该操作即可。我可以用[这个例子](/sf/answers/3487381691/)来反驳,它是专门为破坏垃圾收集器的性能而设计的,并使用可变集合,该集合在幕后使用更多可变对象。除此之外,您找到的示例确实与“*除非您创建了过多的数量*”点完全匹配。进一步注意,游戏循环的主要关注点通常是延迟,而不是吞吐量。 (2认同)