创建数百万个小临时对象的最佳实践

问题

创建(和发布)数百万个小对象的"最佳实践"是什么?

我正在用Java编写国际象棋程序,搜索算法为每个可能的移动生成一个"移动"对象,名义搜索每秒可以轻松生成超过一百万个移动对象。 JVM GC已经能够处理我的开发系统上的负载,但我有兴趣探索以下方法:

  • 最大限度地减少垃圾收集的开销
  • 减少低端系统的峰值内存占用。

绝大多数对象都是非常短暂的,但生成的大约1%的移动是持久化并作为持久值返回,因此任何池化或缓存技术都必须能够排除特定对象的重用。

我不希望完全充实的示例代码,但我希望进一步阅读/研究的建议,或类似性质的开源示例。


#1 热门回答(46 赞)

使用详细垃圾回收运行应用程序:

java -verbose:gc

并且会在收集时告诉你。将有两种类型的扫描,快速扫描和全扫描。

[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]

箭头在尺寸之前和之后。

只要它只是做GC而不是一个完整的GC你就安全了。常规GC是"年轻一代"中的副本收集器,因此不再引用的对象只是被遗忘,这正是你想要的。

ReadingJava SE 6 HotSpot Virtual Machine Garbage Collection Tuning可能对你有所帮助。


#2 热门回答(21 赞)

从版本6开始,JVM的服务器模式采用了6969698207技术。使用它可以避免GC一起使用。


#3 热门回答(18 赞)

好吧,这里有几个问题!
1 - 如何管理短期对象?
如前所述,JVM可以完美地处理大量的短期对象,因为它遵循Weak Generational Hypothesis

请注意,我们说的是到达主内存(堆)的对象。这并非总是如此。你创建的许多对象甚至都没有留下CPU寄存器。例如,考虑这个for循环

for(int i=0, i<max, i++) {
  // stuff that implies i
}

我们不要考虑循环展开(JVM在你的代码上执行的优化)。如果max等于Integer.MAX_VALUE,则循环可能需要一些时间才能执行。但是,i变量永远不会逃脱循环块。因此,JVM会将该变量放入CPU寄存器中,定期递增,但永远不会将其发送回主存储器。

因此,如果只在本地使用它们,那么创建数百万个对象并不是什么大问题。它们在被存储在伊甸园之前就已经死了,所以GC甚至都不会注意到它们。
2 - 减少GC的开销是否有用?
像往常一样,这取决于。

首先,你应该启用GC日志记录,以清楚地了解正在进行的操作。你可以使用-Xloggc:gc.log -XX:+PrintGCDetails启用它。

如果你的应用程序在GC循环中花费了大量时间,那么,是的,调整GC,否则,它可能不值得。

例如,如果你每100毫秒需要一个年轻的GC需要10毫秒,那么你将花费10%的时间在GC中,并且每秒有10个收集(这是huuuuuge)。在这种情况下,我不会花费任何时间进行GC调整,因为那些10 GC / s仍然存在。
3 - 部分经验
我在创建大量给定类的应用程序上遇到了类似的问题。在GC日志中,我注意到应用程序的创建速率大约为3 GB / s,这太过分了(每秒...... 3 GB的数据?!)。

问题:由于创建了太多对象而导致GC过多。

在我的例子中,我附加了一个内存分析器,并注意到一个类占我所有对象的很大比例。我追踪了实例,发现这个类基本上是一对包裹在一个对象中的布尔值。在这种情况下,有两种解决方案:

  • 重做算法,这样我就不会返回一对布尔值,而是我有两个方法分别返回每个布尔值
  • 知道只有4个不同的实例,缓存对象

我选择了第二个,因为它对应用程序的影响最小,很容易引入。我花了几分钟就把一个带有非线程安全缓存的工厂(我不需要线程安全,因为我最终只有4个不同的实例)。

分配率降至1 GB / s,年轻GC的频率(除以3)也是如此。

希望有所帮助!