如何避免 Java 游戏中的垃圾回收延迟?(最佳实践)[已关闭]

我正在为Android平台调整Java中的交互式游戏的性能。偶尔会在垃圾回收的绘制和交互方面遇到一些小问题。通常它不到十分之一秒,但有时在非常慢的设备上它可以大到200毫秒。

我正在使用ddms分析器(Android SDK的一部分)来搜索我的内存分配来自哪里,并从我的内部绘图和逻辑循环中删除它们。

最糟糕的罪犯是短循环,比如,

for(GameObject gob : interactiveObjects)
    gob.onDraw(canvas);

每次执行循环时,都会分配一个。我现在正在为我的对象使用数组()。如果我想要内部循环中的树或哈希,我知道我需要小心甚至重新实现它们,而不是使用Java Collections框架,因为我负担不起额外的垃圾回收。当我查看优先级队列时,可能会出现这种情况。iteratorArrayList

我也遇到了麻烦,我想使用显示分数和进度。这很糟糕,Canvas.drawText

canvas.drawText("Your score is: " + Score.points, x, y, paint);

因为,数组和将被分配到所有以使其工作。如果您有一些文本显示项目,并且每秒运行框架60次,则开始累积并会增加垃圾回收的打嗝。我认为这里最好的选择是保留数组并手动解码或手动将其解码,并将字符串连接到开头和结尾。我想听听是否有更干净的东西。StringscharStringBufferschar[]intdouble

我知道一定还有其他人在那里处理这个问题。您如何处理它,您发现在Java或Android上交互式运行的陷阱和最佳实践是什么?这些gc问题足以让我错过手动内存管理,但不是很多。


答案 1

我做过Java手机游戏...避免GC'ing对象(反过来,GC对象会在某个点触发GC,并杀死游戏的性能)的最佳方法是首先避免在主游戏循环中创建它们。

没有“干净”的方法来解决这个问题,我先举个例子......

通常,例如,屏幕上有4个球,分别位于(50,25),(70,32),(16,18),(98,73)。好吧,这是你的抽象(为了这个例子而简化):

n = 4;
int[] { 50, 25, 70, 32, 16, 18, 98, 73 }

你“弹出”消失的第二个球,你的int[]变成:

n = 3
int[] { 50, 25, 98, 73, 16, 18, 98, 73 }

(请注意,我们甚至不关心“清洁”第4个球(98,73),我们只是跟踪我们剩下的球的数量)。

可悲的是,手动跟踪物体。这是在移动设备上大多数当前性能良好的Java游戏上完成的。

现在对于字符串,我会这样做:

  • 在游戏初始化时,仅使用drawText(...)在数组中保存的数字0到9进行预绘制。BufferedImage[10]
  • 在游戏初始化时,预抽一次“您的分数是:”
  • 如果“您的分数是:”确实需要重新绘制(因为,例如,它是透明的),那么请从您的预存储中重新绘制它BufferedImage
  • 循环计算分数的数字,并在“您的分数是:”之后,手动逐个添加每个数字(通过每次从您预先存储它们的位置复制相应的数字(0到9)。BufferedImage[10]

这为您提供了两全其美的优势:您可以重用drawtext(...)字体,并且在主循环期间创建的对象恰好为零(因为您回避了对drawtext(...)的调用,这本身很可能正在疯狂地生成,嗯,不必要的废话)。

这种“零对象创建绘制分数”的另一个“好处”是,对字体进行仔细的图像缓存和重用并不是真正的“手动对象分配/解除分配”,它实际上只是仔细的缓存。

它不是“干净的”,也不是“好的做法”,但这就是它在一流手机游戏中(比如Uniwar)中的做法。

而且速度很快。快点。比任何涉及创建对象的东西快。

P.S:实际上,如果你仔细看一些手机游戏,你会发现字体实际上不是system/Java字体,而是专门为每个游戏制作的像素完美字体(这里我只是给你一个如何缓存system/Java字体的例子,但显然你也可以缓存/重用像素完美/位图字体)。


答案 2

虽然这是一个2年前的问题...

避免GC滞后的唯一和最佳方法是通过静态分配所有必需的对象(包括在启动时)来避免GC本身。预先创建所有必需的对象,并且永远不要删除它们。使用对象池可重用现有对象。

无论如何,即使您对代码进行了所有可能的优化,也可能最终暂停。因为除了您的应用程序代码之外,其他任何内容仍在内部创建GC对象,这些对象最终将成为垃圾。例如,Java 基本库。即使使用简单的类也可能产生垃圾。(所以应该避免)调用任何 Java API 都可能产生垃圾。当您使用Java时,这些分配是不可避免的。List

另外,由于Java旨在利用GC,因此如果您真的尝试避免使用GC,那么由于缺乏功能,您将遇到麻烦。(甚至类也应该避免)因为它允许GC,所以所有的库都可以使用GC,所以你实际上/实际上没有库。我认为在基于GC的语言上避免GC是一种疯狂的试验。List

最终,唯一实用的方法是下降到较低的水平,在那里你可以完全控制自己的记忆。如C族语言(C、C++等)。所以去NDK。

注意

现在谷歌正在发布增量(并发?GC可以减少很多暂停。无论如何,增量GC意味着只是随着时间的推移分配GC负载,因此如果分布不理想,您仍然会看到最终暂停。此外,由于较少的批处理和分配操作开销的副作用,GC性能本身也会降低。


推荐