Bullet allocates 204.572.529 Vector3f – more than 500MB – Memory leak?

Hello everyone,

first a disclaimer: I know next to nothing about profiling and was just playing around because I noticed high memory requirements by my game. So maybe I did something wrong.

But now to the problem.
When I profiled my game, I saw this screen:

200 MILLION Vector3f? Being responsible for 63,3% percent of allocated bytes?
Something’s not right here, is it?

So I took a look a closer look at how they’ve been created:

Looks like bulletphysics is responsible for pretty much all of them.
Or more exactly: OptimizedBvh.buildTree(…)

This is not normal behavior, is it?
Any ideas?

Oh and by the way:
About ~160 million vector3f objects are allocated during game startup.
After that it keeps going however, but I think only when collisions happen.

Hard to say without knowing what code causes this. Maybe having a simple test case would be better to find out if its an actual jbullet problem.

Here you go:
[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Spatial;
import java.util.logging.Logger;

public class Main extends SimpleApplication {

public static void main(String[] args) {
    Main app = new Main();
    app.start();
}

@Override
public void simpleInitApp() {
	Spatial template = assetManager.loadModel("Models/barracks.j3o");
	
	for (int i=0; i<10; i++) {
		for (int j=0; j<10; j++) {
			Spatial spat = template.clone();
			spat.addControl(new RigidBodyControl(0));
			spat.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(i * 20, 0, j * 20));
			rootNode.attachChild(spat);
			
			Logger.getLogger("").info("created "+ i + " " + j);
		}
	}
}

@Override
public void simpleUpdate(float tpf) {
}

@Override
public void simpleRender(RenderManager rm) {}

}
[/java]

In this test case, barracks.j3o has about 6000 triangles.
If I load 100 of them, I bullet allocates

158MB (93,3% of application total)
62,7 million objects (99,2% of application total)

notice that I didn’t even use BulletAppState or PhysicsSpace. It’s enough to add a RigidBodyControl to the objects.

@m41q said: In this test case, barracks.j3o has about 6000 triangles. If I load 100 of them, I bullet allocates

158MB (93,3% of application total)
62,7 million objects (99,2% of application total)

notice that I didn’t even use BulletAppState or PhysicsSpace. It’s enough to add a RigidBodyControl to the objects.


In Java the concept of leaking memory isn’t the same as in C++. Those are most likely short-lived objects. In other words, they might be allocated in a method and then let go of immediately, causing the garbage collector to go in and reclaim them.

So in reality it is not such a big issue since they will be collected anyway. You can always limit the memory usage of the application by using the -Xmx Java command line parameter, thus causing the GC to run more often.

Of course, the best solution is to switch to using native bullet. Then you would avoid this issue altogether as well as get a performance boost …

Plus, I guess OptimizedBvh.buildTree() (which is what you have highlighted in your screen shot so I assume you mean to highlight it) should be an initialization cost and not anything per frame.

@Momoko_Fan

@Momoko_Fan said: So in reality it is not such a big issue since they will be collected anyway.
This is very easy to test. I run my test case, wait for everything to be created... and click on "Run Garbage Collection in Profiled Application". The allocated bytes and objects of javax.vecmath.Vector3f stays the same. The survinving generations count increases however (which is a sign of a memory leak).

@pspeed

  1. Of course it would be an initial cost, if I didn’t already tell you:
@m41q said: About ~160 million vector3f objects are allocated during game startup. After that it keeps going however, but I think only when collisions happen.
  1. So what it’s an initialization cost? My game is intended to be round after round. If there is a memory leak and the initialization data of the physics of all previous rounds is not collected - and this memory leak amounts to hundreds of millions of objects - it’s easy to see why I need 2 or 3 gigabytes of RAM after playing for some time.

Well, the highlighted bit is the held information in the collision shape. So unless collision shapes are getting created all the time I wouldn’t expect that memory to grow.

Maybe you are creating lots of collision shapes as the game runs?

Either way, if dead objects aren’t getting GC’ed then they aren’t getting removed properly for some reason.

@pspeed
If you want a small test case as well, I modified the previous one:
[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import java.util.logging.Logger;

public class Main extends SimpleApplication implements ActionListener{
BulletAppState bas;

public static void main(String[] args) {
    Main app = new Main();
    app.start();
}

@Override
public void simpleInitApp() {
	inputManager.addMapping("shoot_ball", new KeyTrigger(KeyInput.KEY_E));
	inputManager.addListener(this, "shoot_ball");
	
	Spatial template = assetManager.loadModel("Models/barracks.j3o");
	bas = new BulletAppState();
	stateManager.attach(bas);
	bas.setDebugEnabled(true);

	AmbientLight ambLight = new AmbientLight();
	ambLight.setColor(ColorRGBA.White.mult(4));
	rootNode.addLight(ambLight);
	
	
	for (int i=0; i<10; i++) {
		for (int j=0; j<10; j++) {
			Spatial spat = template.clone();
			spat.addControl(new RigidBodyControl(0));
			spat.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(i * 20, 0, j * 20));
			bas.getPhysicsSpace().add(spat);
			rootNode.attachChild(spat);
			
			Logger.getLogger("").info("created "+ i + " " + j);
		}
	}
}

@Override
public void simpleUpdate(float tpf) {
}

@Override
public void simpleRender(RenderManager rm) {}


@Override
public void onAction(String name, boolean isPressed, float tpf) {
	switch (name) {
		case "shoot_ball":
			if (isPressed) {
				shootBall();
			}
			break;
	}
}

public void shootBall() {
	Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
    Geometry ball_geo = new Geometry("cannon ball",new Sphere(12, 12, 0.2f, true, false));
    ball_geo.setMaterial(mat);
    rootNode.attachChild(ball_geo);
    
	RigidBodyControl rbc = new RigidBodyControl(5f);
    ball_geo.addControl(rbc);
    bas.getPhysicsSpace().add(rbc);
	
    Vector3f camDir = cam.getDirection();
    rbc.setPhysicsLocation(cam.getLocation().add(0,-1,0).addLocal(camDir.mult(3)));
    rbc.setLinearVelocity(camDir.mult(25));
}	

}
[/java]

If you play around with it, you can see the allocated objects increasing when the balls collide with stuff.

@pspeed said: Either way, if dead objects aren't getting GC'ed then they aren't getting removed properly for some reason.
Right. It's not that important if I create some collision shapes during game time or if jBullet uses this method for some reason.

I don’t have time to run test cases and usually I can spot the problem right away. I don’t know bullet that well since I’ve never used it.

Anyway, I don’t see in your test case where the balls are ever removed… so I guess they will keep building up which shouldn’t be surprising, ie: all of those freshly created collision shapes hanging around.

Collisions themselves will generate Vector3fs because they need to report the collision… but those should be temporary and go away on GC. But as for your test case, I’d expect the Vector3fs held by collision shapes to keep growing and growing because they are never removed.

@pspeed sorry I didn’t clarify:
If I add a ball, I can see the memory requirement increase, which is logical, you’re right.
However what I meant was: After adding them - and seeing the memory increase - the memory keeps increasing as long as balls are colliding with each other.
(You know, as long as they’re pink)

Is the problem clear now?

You can see an increase long after they’ve been added.
This stops once they balls stop moving.

Your test case only adds balls, never removes them… And as was said, garbage collection. Each new ball will cause new garbage and when its removed isn’t clear.

@m41q said: @pspeed sorry I didn't clarify: If I add a ball, I can see the memory requirement increase, which is logical, you're right. However what I meant was: After adding them - and seeing the memory increase - the memory keeps increasing as long as balls are colliding with each other. (You know, as long as they're pink)

Is the problem clear now?

You can see an increase long after they’ve been added.
This stops once they balls stop moving.

The problem has always been clear.

“Memory grows, see” points to high memory that will be static with number of collision shapes.

“As new collisions are happening memory keeps growing and growing” but not actually showing that it’s not collision shapes that growing but other garbage that should get GC’ed properly. What was highlighted shouldn’t have anything to do with collisions.

Short of profiling everything for you I’m not sure how to help now. Sounds like you have some work ahead of you.

Put it this way: does the test case you provided eventually crash with an out of memory error if you fire 100 balls and stop and then just let it run with collisions happening?

Yeah, you could test that. Just make a box so the balls don’t fall into oblivion and move one ball by applying forces (or by making it kinematic and moving it in a circle) so it bounces into the others and let the test run for some hours.

Yeah, the previous test case only leads to confusion and unclear results.
I was planning to do something like that, but I’m at work at the moment, so I don’t have time until the evening.

Guys…

I got it all wrong:
I told you the statistics, Bullet allocates 204.572.529 Vector3f objects. They don’t go away with garbage collection.
This is still true.
However I just found out these are allocated objects. Which means in the objects in the statistics are all objects, that have been allocated at some point regardless of them being deallocated again. That’s why garbage collection doesn’t change a thing. Because that’s not what the statistic is about.

:roll: at least I told you I know next to nothing about game profiling in the first line :roll:

Thank you all for your opinions anyway.

2 Likes