LibGdx中的内存使用情况

15

我正在与开发团队一起处理一个大型应用程序,并且内存是早期需要考虑的因素。当我按照原样运行程序时,它大约占用44 MB的内存(从任务管理器中找到)。然后我创建了10,000个实体。现在内存使用量约为83 MB。我有一个方法,可以在按下空格键时销毁这些实体,看起来就像这样。

public static void disposeAllBodies(){
    Array<Body> bodies = new Array<Body>();
    world.getBodies(bodies);
    int destroyCount = 0;
    System.out.println("Attempting to destroy " + world.getBodyCount()+ " bodies");
    for(Body b : bodies){
        world.destroyBody(b);
        destroyCount++;
    }

    System.out.println("Successfully destroyed " + destroyCount + " body(s), " + world.getBodyCount() + " remain");


}

处理所有的物体没有问题,这些都是应用程序中唯一的东西。当它们被销毁后,内存在几秒钟内降至约66MB,然后跳到78MB并保持不变。

所以我想知道有没有更好的方法来处理这些物体?这个应用程序将创建数百万个物体,但大部分都将被销毁,但是如果内存一直上升,它将无法处理这么多的销毁,因为内存基本保持不变。

此外,CPU从0.2%(在任何物体之前)上升到23%(10,000个物体出现时),然后下降到2.3%(销毁物体后)。因此,即使是在销毁物体之后,CPU也需要完成更多的工作。

感谢任何帮助!

更新:创建物体的代码如下:

BodyDef bodyDef = new BodyDef();
    bodyDef.type = type;
    bodyDef.position.set(new Vector2(position.x, position.y));

    Body body = world.createBody(bodyDef);

    FixtureDef fixtureDef = new FixtureDef();
    Fixture fixture;
    if(isCircle){
        CircleShape circle = new CircleShape();
        circle.setRadius(dimensions.x);
        fixtureDef.shape = circle;
        fixture = body.createFixture(fixtureDef);
        circle.dispose();
    }else{
        PolygonShape rectangle = new PolygonShape();
        rectangle.setAsBox(dimensions.x, dimensions.y);
        fixtureDef.shape = rectangle;
        fixture = body.createFixture(fixtureDef);
        rectangle.dispose();
    }

这些仅是Box2D的物体,没有附加精灵或其他任何东西。谢谢!


你能否同时包含你用来创建主体的代码? - Ata Keskin
你只是创建/销毁box2d物体吗?您是否向物体的UserData添加任何内容(例如Entity / Actor实例),这些内容可能不会被释放/处理?当您创建身体时,特别是夹具时,您是否销毁了在创建过程中使用的任何形状对象?(例如shape = new PolygonShape();等;fixtureDef.shape = shape; shape.dispose();)。您是否在不需要且不处理的任何地方创建新纹理?等。 - Peter R
答案已更新伙计们 - Pookie
@Luke,你确定有问题吗?通常情况下,即使在进程内释放了堆分配的内存,也不会将其返回给操作系统,直到进程终止。但是,在后续的分配中,进程将尝试重用先前释放的内存(如果可能),因此进程内存占用量不会增加太多。我无法针对libgdx进行测试,但通常情况下都是这样工作的。 - Ruslan Batdalov
似乎按预期工作(这可能只是JVM开销,GC会处理)。你发布的代码没有问题。但是,如果使用对象池并重复使用对象(例如CircleShape),则可以进行优化。 - p.streef
1个回答

5
你试过简化版的“仅box2d”代码,看看是否仍然有同样的问题吗?我之所以问是因为你还在Changing FixtureDef properties Java Libgdx上发布了另一个关于“更改FixtureDef属性”的问题,并且你提供了更多的整体代码(这个问题的代码是其他问题中代码的子集)。查看那段代码,可能会有一些问题。
在其他问题中,你将bodies、bodyDefs、fixtures和fixtureDefs放入了一个HashMap中,但你没有展示如何检索/清除该映射。这可能会导致内存泄漏。我觉得不太可能,但也不排除。
但我确实看到了这一部分,我相当确定它会引起问题:
public void attachNewSprite(String internalPath){
    entitySprite = new Sprite(new Texture(Gdx.files.internal(internalPath)));
    ((Body)bodyObjects.get(BodyReferences.BODY)).setUserData(entitySprite);
}

在这个问题中,你说你没有使用雪碧图,但是如果你在代码中做了上述操作,每个新的Texture()将会占用内存。你必须明确地处理你创建的每个纹理。你不应该每次创建一个新的Sprite就创建一个新的纹理。理想情况下,你只需创建一次纹理,然后使用Sprite(即TextureRegion)来映射纹理。然后在完成所有任务时(在关卡/游戏等的结尾处)处理纹理。为了处理纹理,你必须保持对它的引用。
编辑/更新:
今天早上我有些时间,所以我拿出你发布的代码,并添加了一些内容来创建一个仅有最少功能的简单应用程序,其中包含你的身体创建和删除代码。我设置了一个定时器,每X秒触发一次,只是为了看看当你创建/销毁10k个身体时会发生什么,你发布的代码似乎没问题。因此,我认为你的问题可能出现在你没有发布的代码中。我的机器上的内存会有些波动(你永远不知道GC什么时候会启动,但它从未真正超过45 MB)。
除非你看到的与你所做的不同(或者如果你有更多的代码要发布等),否则我认为你已经发布的代码没有问题。
import java.util.concurrent.ThreadLocalRandom;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Timer;
import com.badlogic.gdx.utils.Timer.Task;

public class Memory implements ApplicationListener {

    private static World world;

       private static void createNewBodies(boolean isCircle, Vector2 position, Vector2 dimensions) {
            BodyDef bodyDef = new BodyDef();
            //bodyDef.type = type; //all bodies here are dynamic
            bodyDef.type =  BodyType.DynamicBody;
            bodyDef.position.set(position);

            Body body = world.createBody(bodyDef);

            FixtureDef fixtureDef = new FixtureDef();
            Fixture fixture;
            if(isCircle){
                CircleShape circle = new CircleShape();
                circle.setRadius(dimensions.x);
                fixtureDef.shape = circle;
                fixture = body.createFixture(fixtureDef);
                circle.dispose();
            }else{
                PolygonShape rectangle = new PolygonShape();
                rectangle.setAsBox(dimensions.x, dimensions.y);
                fixtureDef.shape = rectangle;
                fixture = body.createFixture(fixtureDef);
                rectangle.dispose();
            }
       }

       public static void disposeAllBodies(){
            Array<Body> bodies = new Array<Body>();
            world.getBodies(bodies);
            int destroyCount = 0;
            System.out.println("Attempting to destroy " + world.getBodyCount()+ " bodies");
            for(Body b : bodies){
                world.destroyBody(b);
                destroyCount++;
            }

            System.out.println("Successfully destroyed " + destroyCount + " body(s), " + world.getBodyCount() + " remain");

        }

       private static void buildAllBodies() {
           int minPos = 10;
           int maxPos = 400;
           int minWidthHeight = 50;

           Vector2 position = new Vector2();
           Vector2 dimensions = new Vector2();

           for (int i=0; i<10000; i=i+2) {
               position.x = ThreadLocalRandom.current().nextInt(minPos, maxPos+1);
               position.y = ThreadLocalRandom.current().nextInt(minPos*2, maxPos*2+1);
               dimensions.x = ThreadLocalRandom.current().nextInt(minWidthHeight, minWidthHeight+1);
               dimensions.y = dimensions.x;
               createNewBodies(true, position, dimensions);
               createNewBodies(false, position, dimensions);
           }
       }

       @Override
       public void create() {

           world = new World ( new Vector2(0.0f, -9.8f), true);

           Timer.schedule(new Task() {
                   @Override
                   public void run() {
                       buildAllBodies();
                       disposeAllBodies();
                   }
              }
               , 1.0f
               , 10.0f //how often to do the cycle (in seconds)
              );
       }

       @Override
       public void render() { }

       @Override
       public void dispose() {
           world.dispose();
       }

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

       @Override
       public void pause() { }

       @Override
       public void resume() { }
}

这是两个不同的项目,我在这个问题中发布的方法不是静态方法也不返回哈希映射。但是为什么获取主体和设置精灵会引起问题? - Pookie
你设置精灵的方式,创建了新纹理,你在任何地方释放它了吗?https://github.com/libgdx/libgdx/wiki/Memory-management - Peter R
你是指这段代码还是先前问题的代码? - Pookie
entitySprite = new Sprite(new Texture(Gdx.files.internal(internalPath))); 实体精灵 = 新建 精灵(新建 纹理(Gdx.文件.内部(internalPath))) - Peter R
1
@Luke,你发布的代码看起来很好/正常。如果你想尝试,我更新了我的答案并提供了一个简单的应用程序。因为我认为它显示了内存正在被释放。所以可能是你代码中的其他问题,而你正在看错地方? - Peter R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接