【问题标题】:garbage collector calls in during rendering a simple screen (libgdx)垃圾收集器在渲染简单屏幕期间调用(libgdx)
【发布时间】:2013-04-29 03:49:17
【问题描述】:

我正在测试我几乎完成的游戏(使用 libgdx 创建)的垃圾收集。 我使用详细 gc 和只有 2mb 堆 VM 选项运行我的桌面版本。

我有点担心地注意到 gc 在屏幕渲染期间每隔一段时间就会启动。

我决定创建一个带有单个舞台的简单屏幕,并向其中添加一个 Image Actor。 没有创建其他对象。我注意到,即使设置如此简单,gc 也会偶尔启动。

使用下面的代码,我在运行大约 5 分钟后得到了两个 gc 调用:

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Image;

public class TestScreen implements Screen {

   private static final float viewportWidth = 40f;
   private static final float viewportHeight = 24f;

   private final Assets assets = new Assets();
   private Stage stage;

   @Override
   public void render(float delta) {
      Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
      stage.act();
      stage.draw();
   }

   @Override
   public void resize(int width, int height) {
   }

   @Override
   public void show() {
      stage = new Stage(viewportWidth, viewportHeight, false);
      Image image = new Image(assets.getMenuSkin(), "stars");
      image.setSize(viewportWidth, viewportHeight);
      image.setPosition(0f, 0f);
      stage.addActor(image);
   }

   @Override
   public void hide() {
   }

   @Override
   public void pause() {
   }

   @Override
   public void resume() {
   }

   @Override
   public void dispose() {
      assets.dispose();
      stage.dispose();
   }
}

这是输出:

[GC [DefNew: 998K->4K(1024K), 0.0014329 secs] 2336K->1359K(3124K), 0.0015340 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 964K->3K(1024K), 0.0005355 secs] 2319K->1358K(3124K), 0.0006174 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

还有总结:

Heap
def new generation total 1024K, used 133K [0x323c0000, 0x324d0000, 0x325c0000)
eden space 960K, 13% used [0x323c0000, 0x323e0918, 0x324b0000)
from space 64K, 5% used [0x324c0000, 0x324c0d10, 0x324d0000)
to space 64K, 0% used [0x324b0000, 0x324b0000, 0x324c0000)
tenured generation total 2100K, used 1355K [0x325c0000, 0x327cd000, 0x329c0000)
the space 2100K, 64% used [0x325c0000, 0x32712c48, 0x32712e00, 0x327cd000)
compacting perm gen total 12288K, used 2520K [0x329c0000, 0x335c0000, 0x369c0000)
the space 12288K, 20% used [0x329c0000, 0x32c36140, 0x32c36200, 0x335c0000)
ro space 10240K, 54% used [0x369c0000, 0x36f3daf0, 0x36f3dc00, 0x373c0000)
rw space 12288K, 55% used [0x373c0000, 0x37a61ce8, 0x37a61e00, 0x37fc0000)

是 OpenGL 数据通过被垃圾收集的数组发送吗?

从我在马里奥的书(开始 Android 游戏)中读到的内容,我认为情况并非如此。 据我记得,Mario 写过一个错误,导致 gc 在这种情况下运行,但它只存在于早期的 Android 版本中。

或者桌面实现运行 gc 而 Android 不运行?

【问题讨论】:

  • 对您正在使用的 openGL 库知之甚少 我想知道 show() 多久被调用一次?它每次都会创建一个新图像,并且可能会在堆中乱扔太多对象,从而迫使 GC 大量启动。
  • show() 仅在应该显示屏幕时调用一次。我倾向于将所有创建逻辑都放在这里,因为当我在类构造函数中创建 libgdx 内容时,我曾经得到一些上下文未初始化的错误。

标签: java garbage-collection libgdx


【解决方案1】:

使用 DDMS 堆跟踪器工具。它将准确地告诉您正在分配什么以及分配给它的回溯。请参阅“跟踪对象的内存分配”: http://developer.android.com/tools/debugging/ddms.html

当然,这将特定于 Android,但由于 99% 的 Libgdx 代码是相同的(例如,所有的 scene2d 都是相同的),它应该突出显示分配来自 Libgdx 还是 Lwjgl,或者甚至 JVM(Hotspot 可能正在后台重新编译方法,我不确定它的分配出现在哪里)。

您使用的是哪个版本的 Libgdx?

scene2d 中有一些代码路径会分配新对象,但它们通常会存储在 Pool 中,因此它们不应该被收集(并且分配最终会减慢并停止)。

【讨论】:

  • 我使用的是 0.9.7,jar 日期为 2012-11-27。由于 libgdx 不断发展,我将尝试更新并查看它是否有帮助。我知道 scene2d 汇集了一些对象,例如 Actions(顺便说一句,它们非常酷)。我的游戏实际上使 gc 每分钟启动 3-5 次,最小堆大小为 2mb,尽管我特别小心不要在我的代码中使用“新”、纯字符串、for 循环中的 java 集合迭代器、自动装箱等。感谢您提供有关跟踪工具的信息。接下来,我实际上要问我应该使用哪一个来获取有关正在发生的事情的完整信息。也许它会帮助我解决问题。
  • 告诉我们您发现了什么!
【解决方案2】:

我决定先分析在 libgdx 桌面上运行上面的代码。 对于 2MB 堆,在应用程序启动后 58 秒和 241 秒发生了两次第一次 GC 调用:

58.163: [GC 58.163: [DefNew: 1024K->19K(1088K), 0.0013214 secs] 2367K->1440K(3328K), 0.0014552 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
241.027: [GC 241.027: [DefNew: 1043K->17K(1088K), 0.0008428 secs] 2464K->1439K(3328K), 0.0009747 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

我在 第二次 使用 MAT 的 GC 调用之前几秒钟和几秒钟后抓取了堆。 在分析输出时,我只发现了无法访问的对象的差异。

这里根据第二次 GC 调用前后对象计数的差异,按降序排列:

Before GC call:                                                    |After GC call:
Class Name                                | Objects | Shallow Heap |Objects | Shallow Heap |

java.nio.DirectFloatBufferU               |    19753|        948144|     864|         41472|
int[]                                     |     2599|        218168|    2507|        207152|
java.lang.Class[]                         |      140|          2384|     133|          2248|
java.lang.reflect.Constructor             |       42|          2688|      35|          2240|
char[]                                    |    11588|        602008|   11584|        601808|
java.lang.String                          |    11237|        269688|   11233|        269592|
java.io.FileDescriptor                    |        4|            96|       1|            24|
java.io.FileInputStream                   |        4|            96|       1|            24|
java.lang.Object                          |        6|            48|       3|            24|
java.lang.ref.Finalizer                   |        4|           128|       1|            32|
java.util.concurrent.atomic.AtomicInteger |        4|            64|       1|            16|
java.lang.reflect.Constructor[]           |        5|           160|       3|            96|

主要的巨大区别是 DirectFloutBufferU。 所以,关于 OpenGL 数据缓冲区被垃圾收集,我似乎是对的。 似乎是这样,因为当我添加更多要渲染的演员时,我得到了更频繁的 GC 调用。

其余的差异是微不足道的。不过我不知道怎么解释。

这是正确的行为吗?

一有时间我就会在 Android 中运行一个类似的测试。

【讨论】:

  • 这对我来说似乎很糟糕。它很可能是 LWJGL 的一部分,而不是 Libgdx 的一部分(或者可能 Libgdx 的某些部分使用了错误的 LWJGL API)。撇开 StackOverflow 不谈,这个“答案”很可能是您原始问题的扩展/编辑,而不是作为“答案”发布。
  • 很抱歉发布“答案”。下次我会照你说的做:)
  • Mario Zechner 本人在这里给出了这个问题的答案:badlogicgames.com/forum/viewtopic.php?f=11&t=8786#p40099。他声称这对于桌面应用程序来说是正常的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多