Thursday, July 28, 2016

Memory usage in LibGdx

Leave a Comment

I am in the middle of a huge application in a team of developers and the memory is something to consider early on. When I run the program as it is, it takes about 44 MB of memory (Found from the Task manager). I then create 10,000 bodies. The memory usage is now about 83 MB. I have a method to destroy the bodies when I click space, this is how it looks.

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");   } 

It disposes all the bodies with no problem, and these were the only things in the application. After they are disposed, the memory goes down to about 66MB for a few seconds then jumps up to 78 MB and stays there.

So I am wondering is there a better way to dispose these bodies? This application will be creating millions of bodies but most will be destroyed, however if the memory just keeps going up, it won't be able to handle this much disposing since the memory stays pretty much static.

Also, the CPU goes from 0.2% (before any bodies) to 23% (when 10,000 bodies present) then dwon to 2.3% (when disposed bodies). So even the CPU is doing more work after the disposing of bodies.

Thanks for any help!

Update: The code for creating the bodies is as follows:

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();     } 

These are all just Box2D bodies, no sprites attached or anything. Thanks!

1 Answers

Answers 1

Did you try a stripped down version of the "box2d only" code you posted to see if it still has the same problem? The reason I ask is that you also posted another question on "changing FixtureDef properties" at Changing FixtureDef properties Java Libgdx and you've given a lot more of your overall code. (The code from this question is a subset of the code from the other question). Looking at that code, there could be some issues.

In the other question you put bodies, bodyDefs, fixtures and fixtureDefs into a HashMap, you don't show how you retrieve/clear the map. That might/might not cause memory leaks. I'd say likely not, but you never know.

But I did see this bit, which I'm pretty sure will cause issues:

public void attachNewSprite(String internalPath){     entitySprite = new Sprite(new Texture(Gdx.files.internal(internalPath)));     ((Body)bodyObjects.get(BodyReferences.BODY)).setUserData(entitySprite); } 

In this question you said you used no sprites, but if you do the above somewhere in your code, each new Texture() will take up memory. You have to explicitly dispose each texture you create. You shouldn't really be creating a new Texture each time you create a new Sprite. Ideally, you create the texture once, then use the Sprite, which is a TextureRegion, to map to the texture. Then dispose the texture when you're all done (at the end of the level/game/etc). In order to dispose the texture, you'll have to keep a reference to it.

Edit/Update:

I had some time this morning so I took your posted code and added a bit to create a barebones simple app with your body creation and body deletion code. I setup a Timer to fire every X seconds just to see what happens when you create/destroy 10k bodies and the code you posted seemed fine. So I think your issue may be elsewhere with code you haven't posted. The memory on my machine would fluctuate a bit (you never really know when the GC will kick in, but it never really topped above 45 MB).

Unless you see something different below than what you're doing (or if you have more code to post, etc), I don't see an issue with what you've shared so far.

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() { } } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment