Uko
Uko

Reputation: 13396

Java3D off-screen rendering memory leak

To enable saving of a snapshot of my 3d canvas, I've extended Canvas3D in the next way:

class OffScreenCanvas3D extends Canvas3D {
    OffScreenCanvas3D(GraphicsConfiguration graphicsConfiguration, boolean offScreen) {
        super(graphicsConfiguration, offScreen);
    }

    public BufferedImage doRender(int width, int height) {      
        BufferedImage bImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        ImageComponent2D buffer = new ImageComponent2D(ImageComponent.FORMAT_RGBA, bImage);
        setOffScreenBuffer(buffer);
        renderOffScreenBuffer();
        waitForOffScreenRendering();
        bImage = getOffScreenBuffer().getImage(); 
        setOffScreenBuffer(null);
        return bImage;
    }

    public void postSwap() {}
}

And I add it as a view to the universe. And a main strategy is described here: http://www.java2s.com/Code/Java/3D/PrintCanvas3D.htm

The problem is in memory leak. My app started to crash and when I tried to profile, I've found out that an instance of OffScreenCanvas3D occupies almost 50MB and the most part of it comes from two ArrayLists. Smaller one contains instances of javax.media.j3d.RenderMolecule and a larger one contains instances of Object each containing javax.media.j3d.RenderAtomListInfo and javax.media.j3d.RenderMolecule.

Can anybody suggest me, what I'm doing wrong?

UPDATE

I want to clarify that even when doRender is not called at all (eg. applications is launched and no more actions are taken) the memory is still accumulating. Below I'll add images that will show the situation better.

First one is a memory graph of an idle running application. memory grap

The second one is a pie chart of memory taken by objects. Here (a) is the memory allocated for an instance of OffScreenCanvas3D, (b) is the memory ocupied by all the other objects. memory pie chart

Also below you can see that dirtyDlistPerRinfoList and dirtyRenderMoleculeList take most of the space. Anything with prefix dirty gives me a feeling of a bad code, I don't know why enter image description here

UPDATE2

It seems that the problem is in the next parts:

  1. Objects of dirtyDlistPerRinfoList are added in the updateCanvasResource method of the RenderBin class. This happens with all the canvas.
  2. dirtyDlistPerRinfoList is cleared in updateDirtyDisplayLists method of the RenderBin class.
  3. updateDirtyDisplayLists is called from doWork (a horrible 1300 lines method) in class Renderer for every Canvas3D that's being rendered.

A thing is that offScreen canvas is not being rendered all the time, but just in the moment when the image is going to be saved. And yes, after saving the image, all the memory accumulated in dirtyDlistPerRinfoList is freed.

So the main question is next:

Data of dirtyDlistPerRinfoList is continuously added to canvas that's not being rendered and so the memory won't be deleted. Is this my fault of Java3D bug?

Upvotes: 2

Views: 937

Answers (4)

InteractiveMesh
InteractiveMesh

Reputation: 11

The JDK tool JConsole allows watching the memory usage of your application. You can perform the garbage collector (GC) and check if memory is freed.

Calling 'java.lang.System.gc()' in your program would have the same effect.

Java 3D applications don't per se have memory leaks. While writing this post the 4-Canvas3D sample program PropellerUniverse is running and is monitored by JConsole. Every few seconds the JRE starts garbage collection automatically. The heap memory usage is at 25 MB and the non-heap memory usage is at 34 MB. The program is now running for 25 minutes. http://www.interactivemesh.org/testspace/j3dmeetsswing.html#heavyweight

Upvotes: 1

InteractiveMesh
InteractiveMesh

Reputation: 11

Your OffScreenCanvas3D is exactly once, after doRender is called, subject of the Renderer.doWork method. You can prove this by overriding preRender(). Add:

@Override
public void preRender() {
    System.out.println("OffScreenCanvas3D preRender !!!!!!!!!!!!!!!");
}

The instance's dirty lists will be cleared afterwards (as you mentioned above) and re-used at the time of next snapshot.

Sometimes names might be misleading. Lists with the prefix "dirty" contain objects that force necessary updates for the next rendering loop.

Java 3D definitely creates a lot of redundant data to split your scene graph data from rendering data. Your benefit amongst others is that you are allowed to call (almost) any Java 3D API method on any user thread at any time. No need to encapsulate Java 3D code in Runnables as it is required for Swing and JavaFX interaction or into other constructs.

Upvotes: 1

parasietje
parasietje

Reputation: 1539

Try to eliminate every step until you arrive at an example that still exhibits the problem. Does the problem still occur when only doing the following?

// Moved this to be a field
BufferedImage bImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
ImageComponent2D buffer = new ImageComponent2D(ImageComponent.FORMAT_RGBA, bImage);

OffScreenCanvas3D(GraphicsConfiguration graphicsConfiguration, boolean offScreen) {
    super(graphicsConfiguration, offScreen);
    // set this just once
    setOffScreenBuffer(buffer);
}

public BufferedImage doRender(int width, int height) {
    renderOffScreenBuffer();
    waitForOffScreenRendering();

    // not necessary, image does not change
    // bImage = getOffScreenBuffer().getImage(); 
    // why is this necessary?
    // setOffScreenBuffer(null);

    return bImage;
}

Upvotes: 0

InteractiveMesh
InteractiveMesh

Reputation: 21

Nothing. A Java 3D application requires a minimum of 25+ MB memory plus the memory needed to store your model(s) in RenderMolecule and RenderAtomListInfo classes. Please be aware that BufferedImages also consume a lot of memory depending on their size. Calling System.gc() from time to time might help. BufferedImages are not that easy to remove.

Upvotes: 2

Related Questions