首页 > 解决方案 > C#/MonoGame - 当我卸载我的游戏关卡时销毁内存中的大对象

问题描述

我正在使用 C#/MonoGame 开发游戏,我想知道如何解决与内存中大型游戏对象相关的垃圾收集问题。

每次我加载一个新游戏时,我都会创建并存储一个World对象,该对象本身具有一个包含我的 LOD 地形系统的四叉树的私有字段。四叉树递归地包含所有可能的子节点的顶点信息,直到我决定我想要的最小级别。这意味着每个新World的创建需要大约 10 秒(在后台线程上完成)并且 RAM 大小达到大约 150MB。

我想保证每次加载新游戏时,旧游戏World都会被处理掉,但据我现在所知,这永远不会发生。我刚刚连续按了六次“加载游戏”对其进行了测试,我的游戏使用的内存达到了 1GB,而且从未下降过。

我知道这可能意味着我仍在引用我的旧World对象,但我看不出这是如何发生的。我的主游戏应用程序有一个私有World的,每次我按“加载游戏”时,它都会从头开始重新创建为我的后台加载程序线程上的一个新对象:

// This belongs to the game and is referenced when I want to interact with and draw the current World
private World _world;
.
.
// Executes on background loader thread when I press 'load game'
LoadGame()
{
    // This is where the old World is replaced with the new one, so I want the old one to be disposed as it contains ~150MB of useless data
    _world = new World(worldParameters);
}
.
.
Draw()
{
    // Rendering the active World in our draw loop
    DrawWorld(_world);
}

我尝试设置_world为 null 并强制 GC 在方法的顶部收集LoadGame(),但这没有任何作用。

有没有一种方法可以强制释放旧对象的内存,或者只是看看我是否在游戏的其余部分中无意中指向了它的任何字段,从而使其保持活力?

标签: c#objectmemory-managementgarbage-collectionmonogame

解决方案


在像 C# 这样的垃圾收集语言中,内存分配可能是一件棘手的事情。具有讽刺意味的是,这些语言的设计目的是让您不必过多考虑内存分配。不幸的是,如果保持一致的帧速率对您的游戏很重要,那么在游戏过程中启动垃圾收集器的游戏可能会让玩家感到沮丧。

我知道有两种方法可以处理此类问题。第一种是自己显式调用垃圾收集器。

GC.Collect();
GC.WaitForPendingFinalizers();

请记住,这是一种快速而肮脏的方法,它可能无法按您期望的方式工作。互联网上的许多人会告诉您,不推荐或强制垃圾收集是一个坏主意。我不是利弊方面的专家,但我只是想让您意识到这是一个选择,因此您可以自己进行研究。

第二种方法是对您的数据结构保持智能。一些圈子可能会称之为“实现自己的内存分配策略”。基本上它归结为预先分配一大块内存并一遍又一遍地重用该空间。

对您而言,确切的策略可能非常简单,也可能非常复杂。我的建议是从你能逃脱的最简单的事情开始,看看它是如何进行的。可能有助于研究的模式之一通常称为“对象池”。


推荐阅读