如何避免Java游戏中的垃圾回收延迟?(最佳实践)
如何避免Java游戏中的垃圾回收延迟?(最佳实践)
关闭。这个问题需要更加聚焦。当前不接受回答。
想要改善这个问题?通过编辑此帖子,使问题聚焦于一个问题。
改进此问题
我正在针对Android平台上的Java交互式游戏进行性能优化。偶尔会因垃圾收集而出现绘制和交互方面的卡顿。通常这不超过0.1秒,但在非常慢的设备上有时会达到200毫秒。
我正在使用Android SDK中的ddms分析器来查找我的内存分配来源,并从我的内部绘图和逻辑循环中将其删除。
最大的问题出在短循环中:
for(GameObject gob : interactiveObjects) gob.onDraw(canvas);
每次执行循环时都会分配一个迭代器。现在我正在使用数组(ArrayList
)来代替。如果我想在内部循环中使用树或哈希表,我知道我需要小心,甚至需要重新实现它们,而不是使用Java集合框架,因为我承受不起额外的垃圾收集。这可能在我看优先级队列时会出现。
我在使用Canvas.drawText
显示得分和进度时也遇到了问题。这是不好的:
canvas.drawText("Your score is: " + Score.points, x, y, paint);
因为为了使它工作,将会分配Strings
、char
数组和StringBuffers
。如果您有一些文本显示项目,并且每秒运行60帧,那么这将会增加并增加垃圾回收卡顿。我认为最好的选择是保留char[]
数组,并将您的int
或double
手动解码并将字符串连接到开头和结尾。如果有更好的解决方案,我想听听。
我知道一定有其他人正在处理这个问题。您是如何处理它的?您发现运行Java或Android的交互式实践中的缺陷和最佳实践是什么?这些垃圾收集问题足以让我错过手动内存管理,但是并不会太多。
虽然这是一道两年前的问题......
避免GC延迟的唯一和最好的方法是通过在某种程度上静态分配所有所需的对象(包括在启动时)来避免GC本身。预先创建所有所需的对象,并永远不要将它们删除。使用对象池来重复使用现有对象。
无论如何,即使在您对代码进行了所有可能的优化之后,您还是可能会遇到暂停。因为除了您的应用程序代码之外的任何其他内容都在内部创建GC对象,这些对象最终会成为垃圾。例如,Java基础库。即使是使用简单的List
类也可能会创建垃圾。(因此应避免使用)调用任何Java API都可能会创建垃圾。在使用Java时,这些分配是无法避免的。
此外,由于Java旨在利用GC,如果您确实尝试避免GC,您将面临缺少功能的问题。(甚至List
类应该避免使用)因为它允许GC,所有库都可以使用GC,因此您实际上几乎没有可用的库。我认为在基于GC的语言中避免GC是一种疯狂的尝试。
最终,唯一实际的方法是进入更低级别,完全控制自己的内存。例如C家族语言(C、C++等)。所以去NDK吧。
注意
现在Google正在发布增量(并发?)GC,可以大大减少暂停时间。无论如何,增量GC意味着只是随着时间分配GC负载,所以如果分配不理想,您仍然会看到暂停。此外,由于较少批量和分布操作开销的副作用,GC性能本身将会降低。
我曾经参与过Java手机游戏的开发...避免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 }
(注意我们甚至不关注如何“清理”第四个球(98,73),我们只需追踪剩余的球的数量)。
很遗憾,要手动跟踪这些对象。这就是在大多数目前流行的Java手机游戏中处理的方式。
现在对于字符串,这是我要做的:
- 在游戏初始化时,使用drawText(...)仅一次绘制并保存数字0到9,将它们存储在
BufferedImage[10]
数组中。 - 在游戏初始化时,一次绘制"Your score is: "
- 如果“Your score is:”确实需要重新绘制(因为它是透明的),则从预存的
BufferedImage
中重新绘制它。 - 循环计算得分的数字,并在“Your score is:”之后手动添加每个数字(通过从预存的
BufferedImage[10]
中复制每个数字(0到9))。
这为您提供了最好的两个世界:您可以重用drawtext(...)字体,并且在主循环期间完全没有创建对象(因为您也避免了调用drawtext(...),它本身可能非常差地创建了不必要的垃圾)。
这种“零对象创建绘制分数”的另一个“好处”是,仔细缓存和重复使用字体图像实际上并不是“手动对象分配/释放”,而只是仔细的缓存。
这不是“干净”的,也不是“良好的实践”,但这就是在顶级手机游戏(比如Uniwar)中的做法。
而且速度非常快。比涉及对象创建的任何内容都要快。
P.S:实际上,如果你仔细看一些手机游戏,你会发现通常字体实际上不是系统/Java字体,而是为每个游戏专门制作的像素完美字体(这里我只是给你举例说明如何缓存系统/Java字体,但显然你也可以缓存/重复使用像素完美/位图字体)。