Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/heroku/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何避免Java游戏中的垃圾收集延迟?(最佳做法)_Java_Android_Garbage Collection_Performance - Fatal编程技术网

如何避免Java游戏中的垃圾收集延迟?(最佳做法)

如何避免Java游戏中的垃圾收集延迟?(最佳做法),java,android,garbage-collection,performance,Java,Android,Garbage Collection,Performance,我正在为Android平台调整Java交互式游戏的性能。偶尔在垃圾收集的绘图和交互过程中会出现问题。通常它不到十分之一秒,但在速度非常慢的设备上,有时它可以达到200毫秒 我正在使用ddms profiler(Android SDK的一部分)搜索内存分配的来源,并从内部绘图和逻辑循环中删除它们 最严重的违规者是短环,就像 for(GameObject gob : interactiveObjects) gob.onDraw(canvas); 每次执行循环时,都会分配一个迭代器。我现在正

我正在为Android平台调整Java交互式游戏的性能。偶尔在垃圾收集的绘图和交互过程中会出现问题。通常它不到十分之一秒,但在速度非常慢的设备上,有时它可以达到200毫秒

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

最严重的违规者是短环,就像

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

在使用
Canvas.drawText
显示分数和进度时,我也遇到了问题。这很糟糕

canvas.drawText("Your score is: " + Score.points, x, y, paint);
因为
Strings
char
数组和
StringBuffers
将被分配到所有位置以使其工作。如果您有一些文本显示项,并且每秒运行帧60次,则开始累积,这将增加您的垃圾收集中断。我认为最好的选择是保留
char[]
数组,手动将
int
double
解码到数组中,并将字符串连接到开头和结尾。我想听听有没有更干净的


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

我曾经在Java手机游戏上工作过。。。避免GC’ing对象(这反过来会在某个点触发GC并杀死游戏的性能)的最好方法就是首先避免在主游戏循环中创建它们

没有“干净”的方法来处理这个问题,我将首先给出一个例子

通常情况下,比如说,屏幕上有4个球(50,25),(70,32),(16,18),(98,73)。好的,这里是您的抽象(为了本例而简化):

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

n = 3
int[] { 50, 25, 98, 73, 16, 18, 98, 73 }
(请注意,我们甚至都不关心“清洁”第四个球(98,73),我们只是跟踪剩下的球的数量)

遗憾的是,手动跟踪物体。这是如何在移动设备上运行的大多数当前性能良好的Java游戏上实现的

现在对于字符串,我要做的是:

  • 在游戏初始化时,仅使用drawText(…)对保存在
    BuffereImage[10]
    数组中的数字0到9进行一次预绘制
  • 在游戏初始化时,预先绘制一次“你的分数是:”
  • 如果“您的分数是:”确实需要重新绘制(因为,比方说,它是透明的),那么请从预存储的
    BufferedImage
  • 循环计算分数的位数,并在“您的分数为:”之后逐个手动添加每个位数(每次复制
    BufferedImage[10]
    中预存储的相应位数(0到9)
这给了你最好的两个世界:你得到了重用drawtext(…)字体,你在主循环中创建了零个对象(因为你还避开了对drawtext(…)的调用,而drawtext(…)本身很可能会产生不必要的垃圾)

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

它不是“干净的”,也不是“良好的实践”,但在顶级手机游戏(比如Uniwar)中就是这样做的

而且它很快。该死的快。比任何涉及物体创造的东西都快


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

如果您不想按照建议预先呈现文本,
drawText
接受任何
CharSequence
,这意味着我们可以自己智能地实现它:

final class PrefixedInt implements CharSequence {

    private final int prefixLen;
    private final StringBuilder buf;
    private int value; 

    public PrefixedInt(String prefix) {
        this.prefixLen = prefix.length();
        this.buf = new StringBuilder(prefix);
    }

    private boolean hasValue(){
        return buf.length() > prefixLen;
    }

    public void setValue(int value){
        if (hasValue() && this.value == value) 
            return; // no change
        this.value = value;
        buf.setLength(prefixLen);
        buf.append(value);
    }


    // TODO: Implement all CharSequence methods (including 
    // toString() for prudence) by delegating to buf 

}

// Usage:
private final PrefixedInt scoreText = new PrefixedInt("Your score is: ");
...
scoreText.setValue(Score.points);
canvas.drawText(scoreText, 0, scoreText.length(), x, y, paint);

现在,绘制分数不会导致任何分配(除了开始时可能需要增加
buf
s内部数组时的一次或两次,以及
drawText
的任何分配).

在避免GC暂停非常关键的情况下,您可以使用的一个技巧是在您知道暂停无关紧要的时候故意触发GC。例如,如果垃圾密集型的“showScores”函数在游戏结束时使用,用户不会因为显示记分屏幕和开始下一场游戏之间额外的200毫秒延迟而过度分心……因此,在绘制记分屏幕后,您可以调用
System.gc()

但是如果你使用这个技巧,你需要小心地只在GC暂停不会让人讨厌的地方使用。如果你担心耗尽手机的电池,就不要这样做


不要在多用户或非交互式应用程序中这样做,因为这样做很可能会降低应用程序的整体运行速度。

我自己创建了一个无垃圾版本的
String.format
,至少有一种。您可以在这里找到它:(请原谅德国的评论)

像这样使用它:

GFStringBuilder.format("Your score is: % and your name is %").eat(score).eat(name).result
所有内容都写入一个
char[]
数组中。我必须手动实现从整数到字符串的转换(逐位)以获得
GFStringBuilder.format("Your score is: % and your name is %").eat(score).eat(name).result
for(GameObject gob : interactiveObjects)
    gob.onDraw(canvas);
for (int i = 0; i < interactiveObjects.size(); i++) {
    interactiveObjects.get(i).onDraw();
}