【问题标题】:Object Pooling in JavaJava中的对象池
【发布时间】:2010-10-15 09:30:29
【问题描述】:

维护一个经常使用的对象池并从池中获取一个而不是创建一个新对象的优点和缺点是什么。类似于字符串实习的东西,除了它对所有类对象都是可能的。

例如,它可以被认为是好的,因为它节省了 gc 时间和对象创建时间。另一方面,如果从多个线程中使用它可能会成为同步瓶颈,需要显式释放并引入内存泄漏的可能性。通过占用可以回收的内存,它会给垃圾收集器带来额外的压力。

【问题讨论】:

  • cmets 似乎是负面的,但我正在考虑类似的事情。我有一个 j2me 应用程序,可以创建数千个小的潜在边界框对象。如果我可以创建一个池,那么以后我会给 GC 一个更轻松的工作。我想知道在受限的手机世界中这是否仍然是一个坏主意。
  • 仅池化昂贵的对象(如数据库连接等)。 “昂贵”的定义很大程度上取决于您的要求。

标签: java memory-management garbage-collection


【解决方案1】:

优化第一定律:不要这样做。第二定律:除非您确实已经衡量并知道需要优化的地方以及需要优化的地方,否则不要这样做。

只有当对象的创建成本真的很高,并且它们实际上可以被重用(您可以只通过公共操作将状态重置为可以重用的东西)时,它才会有效。

你提到的两个好处并不是真的:java中的内存分配是free(成本接近10个cpu指令,这不算什么)。所以减少对象的创建只会节省你在构造函数中花费的时间。这对于可以重用的重对象(数据库连接、线程)来说可能是一个好处,而无需更改:您重用 same 连接,同一个线程。

GC 时间没有减少。事实上,情况可能更糟。使用移动的分代 GC(Java 是或达到 1.5),GC 运行的成本取决于活动对象的数量,而不是释放的内存。活动对象将被移动到内存中的另一个空间(这就是内存分配如此之快的原因:空闲内存在每个 GC 块内是连续的)在被标记为 old 并移动到较旧的空间之前代内存空间。

编程语言和支持(如 GC)在设计时考虑了常见用法。如果您在许多情况下避开常见用法,您最终可能会得到更难阅读且效率较低的代码。

【讨论】:

  • 我实际上有兴趣在延迟敏感的实时应用程序中通过停止世界垃圾收集器来降低任何阻塞。你说的很有道理,我同意基准对于做出这样的决定是必要的。
  • GC 时间 可以 减少 - 例如,如果通过使用池,您可以避免分配对象,这可能是低延迟应用程序的重大胜利.确保你永远不会分配真的很痛苦......
  • (我同意它也很容易使 GC 时间变得更糟,顺便说一句。)
  • 这是一个微妙的问题。如果对象最终处于“中间”(它们在第一次 GC 中幸存下来并因此被移动),那么这将比只分配一次更昂贵。但真正利用这一事实的设计将很难解决微妙的问题。
【解决方案2】:

除非创建对象的成本很高,否则我不会打扰。

好处:

  • 创建的对象更少 - 如果创建对象的成本很高,这可能很重要。 (典型的例子可能是数据库连接,其中“创建”包括与服务器建立网络连接、提供身份验证等)

缺点:

  • 更复杂的代码
  • 共享资源 = 锁定;潜在瓶颈
  • 违反 GC 对对象生命周期的预期(大多数对象的生命周期都很短)

您是否有要解决的实际问题,或者这是推测性的?除非您的基准/配置文件运行表明存在问题,否则我不会考虑这样做。

【讨论】:

  • 我实际上有兴趣在延迟敏感的实时应用程序中通过停止世界垃圾收集器来降低任何阻塞。
  • 好吧,你可以看看实时Java——我自己从未使用过它,但值得一试。尝试调整 GC - 那里有很多选项。当然,如果您可以确保您的代码不会在延迟敏感位内分配 任何 对象,那很好 - 但很难。
  • 是的,我看过 java RTS,但不倾向于使用它,主要是因为它不是免费的。 GC 调优也是我一直在尝试的东西,尤其是与 CMS gc 一起玩。只是我可能没有达到正确的最佳参数组合。感谢您的所有帮助,乔恩。
【解决方案3】:

池化意味着您通常不能使对象不可变。这会导致防御性复制,因此您最终会制作比仅制作新的不可变对象时更多的复制品。

不可变性并不总是可取的,但通常你会发现事物可以是不可变的。让它们不是不可变的,以便您可以在池中重复使用它们可能不是一个好主意。

所以,除非您确定这是一个问题,否则不要打扰。使代码清晰易懂,并且它可能会足够快。如果不是这样,那么代码清晰且易于遵循这一事实将使其更容易加快速度(通常)。

【讨论】:

  • +1 了解我的痛苦。我刚刚通过删除对象池解决了一个严重的数据损坏错误。在 Java 1.3 和 1.4 上的分析表明池是一个巨大的胜利,在 1.5 上它会减慢代码速度。
【解决方案4】:

不要。

这是 2001 年的想法。现在唯一仍然有价值的对象“池”是单例。我仅使用单例来减少出于分析目的而创建的对象(因此我可以更清楚地看到影响代码的因素)。

你只是为了没有好的目的而碎片化记忆。

继续运行关于创建 1,000,000 个对象的配置文件。微不足道。

Old article here.

【讨论】:

  • 鉴于构造函数后的初始化时间微不足道。
【解决方案5】:

这完全取决于创建对象的成本,与创建它们的次数相比...例如,只是美化结构的对象(例如,仅包含几个字段,除了访问器之外没有其他方法)可以成为池的真正用例。

一个真实的例子:我需要从生成大量整数/等级对的过程中重复提取 n 个最高等级的项目(整数)。我在有界优先级队列中使用了一个“对”对象(一个整数和一个浮点等级值)。重用对,与清空队列、丢弃对并重新创建它们相比,性能提高了 20%……主要是在 GC 费用方面,因为在 JVM 的整个生命周期中不需要重新分配对。

【讨论】:

    【解决方案6】:

    对象池通常只适用于昂贵的对象,例如数据库连接。在 Java 1.4.2 之前,对象池可以提高性能,但从 Java 5.0 开始,对象池更有可能损害性能而不是帮助,并且通常会删除对象池以提高性能(和简单性)

    【讨论】:

      【解决方案7】:

      我同意 Jon Skeet 的观点,如果您没有特定的理由来创建对象池,我不会打扰。

      在某些情况下,游泳池确实很有帮助/必要。如果您有一个创建成本很高但可以重复使用的资源(例如数据库连接),那么使用池可能是有意义的。此外,在数据库连接的情况下,池对于防止您的应用打开过多的数据库并发连接很有用。

      【讨论】:

        猜你喜欢
        • 2013-02-10
        • 1970-01-01
        • 2019-09-29
        • 2015-03-27
        • 1970-01-01
        • 1970-01-01
        • 2016-05-01
        • 2023-04-05
        • 1970-01-01
        相关资源
        最近更新 更多