Java中的对象池技术

16

维护一个经常使用的对象池,从中获取一个对象而不是新建一个对象的优缺点是什么?这有些类似于字符串驻留,但适用于所有类对象。

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


评论看起来很消极,但我正在考虑类似的事情。我有一个创建数千个小潜在边界框对象的j2me应用程序。如果我可以创建一个对象池,那么我以后就可以让垃圾回收更轻松地完成工作。我想知道在移动设备受限的世界中,这是否仍然是一个坏主意。 - izb
仅池化昂贵的对象(如数据库连接等)。"昂贵"的定义强烈依赖于您的需求。 - Thorbjørn Ravn Andersen
7个回答

24

优化的第一定律:不要进行优化。第二定律:除非你已经测量并确切知道需要在哪里进行优化。

只有当对象创建成本非常高,并且它们可以被重用(只需使用公共操作将其状态重置为可重用状态)时,才能起到作用。

您提到的两个好处并不是真正的好处:Java中的内存分配是免费的(成本接近于10个CPU指令,这几乎可以忽略不计)。因此,减少对象的创建只能节省在构造函数中花费的时间。对于可以重复使用的真正重型对象(如数据库连接、线程),这可能会带来好处:您可以重用相同的连接、相同的线程。

GC时间不会减少。事实上,它可能会更糟。对于移动代GC(Java是,或者直到1.5),GC运行的成本取决于存活对象的数量,而不是释放的内存。存活对象将在被标记为“旧”的之前,在内存中的另一个空间中移动几次(这就是使内存分配变得如此快的原因:每个GC块内的空闲内存是连续的),然后被移入较老的代内存空间。

编程语言和支持,如GC,是针对普遍使用而设计的。如果你在许多情况下偏离常规用法,你可能会得到更难读的代码,也不够高效。


我对降低延迟敏感的实时应用中的任何阻塞非常感兴趣,例如停止世界垃圾收集器。不过,你所说的确有很多道理,我同意需要进行基准测试才能做出这样的决定。 - baskin
2
GC时间可以被减少 - 如果使用池可以避免需要分配对象,例如,在低延迟应用程序中,这可能是一个重大的胜利。但确保永远不分配对象真的很麻烦... - Jon Skeet
顺便说一句,我同意这可能会使GC时间更长。 - Jon Skeet
这是一个微妙的问题。如果对象最终处于“中间状态”(它们在第一次GC后仍然存在并被移动),那么它们的分配成本将比仅分配一次更高。但是,真正充分利用这一事实的设计会很困难,并且会引起微妙的问题。 - David Rodríguez - dribeas

20

除非创建该对象的成本很高,否则我不会费心。

好处:

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

缺点:

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

您是否有实际要解决的问题,还是这只是一种推测?除非您进行了基准测试/分析运行,否则我不会考虑执行此类操作。


我对在延迟敏感的实时应用程序中通过停止世界垃圾收集器等方式降低任何阻塞感兴趣。 - baskin
你可以看看实时Java - 我自己从未使用过,但值得一试。也可以尝试调整垃圾回收器(GC)- 有很多选项可供选择。当然,如果您能确保您的代码在延迟敏感的部分内不分配任何对象,那就太好了 - 但这很难。 - Jon Skeet
是的,我看过Java RTS,但不倾向于使用它,主要是因为它不是免费的。 GC调优也是我一直在尝试的事情,特别是玩弄CMS gc。只是我可能没有找到正确的最佳参数组合。感谢您的所有帮助Jon。 - baskin

9
池化意味着通常情况下你不能使对象不可变。这会导致防御性复制,因此最终你需要制作比只制作一个新的不可变对象更多的副本。
虽然不是总是需要不可变性,但你会发现大多数情况下事物都可以是不可变的。为了让它们可以在池中重用而使它们不可变可能不是个好主意。
所以,除非你确定这是一个问题,否则就不要费心。让代码清晰易懂,往往就足够快了。如果它不够快,那么代码清晰易懂的事实将使其更容易加速(一般而言)。

1
+1 懂我的痛苦。我刚刚通过移除对象池解决了一个严重的数据损坏 bug。在 Java 1.3 和 1.4 上进行分析显示,对象池是一个很大的优势,在 1.5 上它会减慢代码速度。 - Darron

5
不要这样做。
这是2001年的思维方式。现在唯一有用的“对象池”只有一个——单例模式。我仅使用单例模式来减少对象创建,以便进行性能分析(这样我可以更清楚地看到代码的影响)。
除此之外,你只会为没有好处的目的而使内存碎片化。
尝试创建1000000个对象并运行性能分析,结果微不足道。
参考链接:旧文在此

1
由于后构造函数初始化时间微不足道。 - Thorbjørn Ravn Andersen

5

完全取决于创建对象的成本与创建次数的比较……例如,仅包含一些字段且没有方法(除了访问器)的对象可以是池化的一个真实用例。

一个真实的例子:我需要重复地从生成大量整数/排名对的过程中提取前n个最高排名的项目(整数)。我在有界优先级队列中使用了一个“pair”对象(一个整数和一个浮点排名值)。重复使用这些配对对象而不是清空队列、丢弃配对对象并重新创建它们,可提高20%的性能...主要体现在GC负担上,因为这些配对对象在JVM的整个生命周期中都不需要重新分配。


3

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


2

我同意Jon Skeet的观点,如果您没有特定的理由创建对象池,那么就没必要费力去做了。

但是,在某些情况下,对象池确实非常有用/必要。例如,如果您有一个需要花费大量成本来创建但可以重复使用的资源(例如数据库连接),那么使用对象池可能是明智的选择。此外,在数据库连接的情况下,对象池有助于防止您的应用程序打开太多并发连接到数据库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接