Java Heap 在游戏中增长然后重置是否正常?

Is it normal when Java Heap grows during the game and then resets?

它应该保持不变吗?本机堆不会改变。我使用 Gdx.app.getJavaHeap();重置不会经常发生,大约每隔几分钟发生一次,具体取决于游戏。当游戏对象在移动时,它会增长得更快。

当您创建大量新对象然后不再使用它们时,这是正常行为。在有足够多的对象像这个垃圾收集器被执行并清理它们之后。 (更好的描述是here。)

它非常好并且工作没有问题,只是当垃圾收集发生时你可能会遇到小延迟。如果你想解决这个问题,那么 LibGDX 有对象池。您可以阅读更多相关信息 here

这种行为是完全正常的,假设它不会开始影响您的运行时性能(垃圾收集会影响您的帧时间)。

有很多方法可以减少对象的运行时分配,其中之一是避免一次创建新对象,这会影响游戏的运行。

[正在加载屏幕]

使用加载屏幕来预分配和处理大对象或大量小对象,这将确保当您开始处理游戏的渲染和逻辑时,您的程序将不必等待内存 allocation/GC 暂停。

[对象池]

例如,您可以在缓存池中预先实例化对象,而不是创建新对象,只需借用这些实例,按您认为合适的方式使用它们,当您不再需要它们时,您可以释放它们他们回到池中再次使用。

LibGDX 有一个有效的工具,开箱即用,事实上广泛使用它。您可以阅读有关它以及如何使用它的更多信息 here

例如,我经常在屏幕space和世界space之间来回进行大量坐标投影,这涉及到使用大量临时Vector2对象来存储中间值。在程序执行结束时,我得到一份使用报告(自定义调试代码,不包含在发布版本中),告诉我是否所有来自特定池的借用者都已归还他们的实例,该类型被借用了多少次,以及有多少分配了实际实例:

TYPE <Vector2Poolable> [OK] -> Borrowed [45948], Returned [45948], Free [38], Peak free [38]

这是从 60 秒的执行开始的,如您所见,我只实例化了 38 个 Vector2Poolable 对象,这些对象在该时间跨度内被使用了约 46k 次。

别搞错了,这里的目标并不是严格节省内存使用量,这里的关键要点是我避免将每分钟计算和管理约 50k 小实例的负担转嫁到 JVM 上。这会迅速堆积并开始影响您的帧时间。

[字符串是你的敌人]

Java 中的字符串是由 char 数组内部支持的对象。由于数组是不可变的(无法增长),因此字符串也是不可变的。每当对 String 进行更改时,都会创建一个全新的 String。这意味着看似非常便宜的操作,以每秒 60 次的速度重复,可以为 GC 生成大量工作。

这在通过 + 运算符连接字符串时更为明显,这至少会导致一次分配。

最好的做法是使用 LibGDX 的 StringBuilder class 之类的东西进行连接,并尽可能重复使用它(如果你可以,每个 class我们不关心并发性,否则您可以将其包装在 ThreadLocal 变量中)。 StringBuilder 的方法类似于 Pooling,因为它仅在需要时才增加后备存储,因此在执行过程中的某个时刻,它不必为您的字符串操作需要重新分配内存。

不要低估字符串操作对垃圾收集的影响,Libgdx中的大多数Scene2D.ui class(例如Label)直接接受StringBuilder实例并非偶然,因此更新它们甚至不需要从 StringBuilder 的缓冲区分配一个字符串来使用它(如所见 here,通过简单地在现有缓冲区中复制数据来最小化分配)

[最后的想法] 你不知道你不知道什么,这就是为什么,特别是在游戏开发中,无论使用何种语言,强烈建议你分析你的代码和内存分配。存在查看程序运行时发生情况的解决方案,这些是我最喜欢的一些解决方案:

  • JVisualVM完全免费,足以完成大多数分析任务。
  • YourKit Java Profiler 需要许可证,而且对于非专业开发人员来说可能过于昂贵,但它是我曾经用来分析 java 应用程序的最强大的工具之一。绝对值得一试。