Skip to main content

Garbage Collect J3D Resources

23 replies [Last post]
jeeky
Offline
Joined: 2003-06-15

Hi Folks,

I am trying to figure out how to reclaim the resources generated from a Java3D program in general and from a Texture specifically. I have a spike solution shows a simple textured sphere on a Canvas3D. Once I dispose of the JFrame containing the Canvas3D I would expect all resources to be garbage collected sooner or later. Instead, it appears that only some items are GC'd and others are not(In particular, I think textures are not). When I profile the program using NetBeans, I get a large number of leftover character arrays and hashmap entries, which I assume are in the J3D objects.

My spike solution has a panel that dumps memory state every 5 seconds and buttons to allocate the Canvas3D JFrame and to force garbage collection. When I run it, I generally get results something like this:
Initial state: ~0-2 MB Heap Used
After allocation: ~28 MB Used
After initial allocation: ~15 MB Used
After JFrame disposal and GC: ~14 MB Used (Why doesn't this go down to 0-2 again?)

To get the spike solution working, simply create files for the following 3 classes then create a texture image named "image.png" in a package named "j3dheaptester". There are 4 files you need (The driver, the JFrame containing the Canvas3D, an Appearance containing a texture, and a texture image). The class I have been focusing on is TextureAppearance.java since my guess is that I can't get the bytes in my texture back.

Any ideas as to how to get these items GC'd?

Message was edited by: jeeky
I don't see an obvious attachment button, so I'll paste the code.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
jeeky
Offline
Joined: 2003-06-15

If you look at the dispose method, I do call universe.cleanup() (I accidentally stated dispose in my edit comment, but I do indeed call cleanup). I tried adding a window adapter (shown below) to the constructor (I also removed the same lines in the dispose method so a NPE doesn't get thrown) just in case you must call cleanup before entering dispose. The problem still exists.

[code]
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.out.println("Cleaning up");
universe.cleanup();
System.out.println("Cleaned up");

remove(canvas);

canvas = null;
universe = null;
}
});
[/code]

Thanks for the suggestion. Any other ideas?

aces
Offline
Joined: 2003-07-17

Before you dispose your frame, call simpleUniverse.cleanup().
After your dispose your Frame, run System.gc() and System.runFinalization() to freed up pending resources.

stylertim
Offline
Joined: 2006-05-04

I'm a little troubled. I'm using SimpleUniverse and a Canvas3D, added to a JPanel which itself is added to a GUI within a top-level JFrame.

For test purpose I commented out everything except for the SimpleUniverse and Canvas3D instantiation and a JPanel.add(c3d) invokation and JPanel.setLayout(new GridLayout( )).

This consumes around 10 MBs. When I remove the panel, I first call a self-defined method void cleanUp( ) which invokes universe.cleanup( ), universe = null, remove(canvas) and canvas = null which should completely release all referenced objects.

After this call I finally remove the panel and clear the reference by "nulling" the panel object, call System.runFinlization( ) and System.gc( ). And...nothing happens.

The panel's finalizer reports it's successfully discarded, the resources used by the panel are released. The memory consumed by SimpleUniverse and Canvas3D seems to stay occupied.

Well, I tested this with JRE 1.6.0 Updates 2, 3 and EA and JRE 1.7.0_b22 under Windows Vista.

I'm really not sure how to proceed cause I'm absolutely out of ideas and almost inclined to support jeeky's bug thesis. A hint anyone?

- Thomas

jeeky
Offline
Joined: 2003-06-15

Yeah, this one is throwing me for a loop. I think I explicitly clean up just about every resource I have access to in this example and I still don't get the resources back. This is a big problem if you need a non-persistent set of J3D windows in your application. Do any of the moderators have thoughts on this? If it looks like a bug I'd be glad to submit a report, but I also don't want to just say it's a bug because I can't figure it out (even though it seems like I am not alone on this one).

stylertim
Offline
Joined: 2006-05-04

Which system configuration are you running on? I wonder if this problem is JRE/J3D version or OS dependant.

- Thomas

jeeky
Offline
Joined: 2003-06-15

I can reproduce this problem on both Mac OS X and Windows XP machines. The Mac has Java 1.5 and the XP box has 1.6. J3D version is 1.5 or 1.5.1, I'll have to take a look and confirm, but I think the latter.

zesharp
Offline
Joined: 2006-12-21

Strange, but I was unable to see any resonable memory leak on XP. I used JConsole to check real memory consume.

After first run of Java3D, the J3D thread stay alive, so it retains some live objects. Of course I guess there are very good reasons for this thread beeing alive, so I do not consider it bug.

I didn't test it on Linux yet.

stylertim
Offline
Joined: 2006-05-04

I have to confirm. Profiled the application and found out that everything is handled correctly. The Windows Task Manager was troubled the most but it turns out I did nothing wrong according to this post

http://forums.java.net/jive/thread.jspa?threadID=32315&tstart=0

which I opened after investigating the memory consumption with the profiler. It seems everything J3D related is handled correctly so far.

jeeky, please profile your application and report your findings here. If you want you can send me the source files to "tkranz at uni-koblenz.de".

- Thomas

jeeky
Offline
Joined: 2003-06-15

Confirm that nothing is wrong or something is wrong?

I did profile the application again using the thread profiler. It appears that there are two threads, J3D-Renderer-1 and TimerQueue that never die. I now call canvas.stopRenderer() but that doesn't seem to help. I think I need to kill those threads to make everything go away. Does that sound right?

Thanks

kcr
Offline
Joined: 2004-03-17

You are correct that the J3D-Renderer-1 thread never goes away. However, the memory it consumes is a minimal, fixed overhead. It does not hold any references to your scene graph once all canvases are properly cleaned up. The TimerQueue thread is not a Java 3D thread, so I don't know about that.

We are not aware of any memory leaks in Java 3D as of Java 3D 1.5.1. If you discover one, please file an issue.

puybaret
Offline
Joined: 2004-12-04

I didn't look in detail to your program, but I noticed you didn't call SimpleUniverse [b]cleanup[/b] method at frame disposal. I had the same problem in Sweet Home 3D, and once I used that method, memory used by Java 3D was correctly garbage collected.
If you want an example, look at com.eteks.sweethome3d.swing.HomeComponent3D class that layouts a Canvas3D in a JComponent. Its code is at http://sweethome3d.cvs.sourceforge.net/sweethome3d/SweetHome3D/src/com/e... , and the universe it uses is cleaned up in the ancestorRemoved method of an AncestorListener added to the component.

jeeky
Offline
Joined: 2003-06-15

Thanks for the suggestion. I've modified the above code to reflect it. However the problem still persists :(. Any other ideas?

griffenjbs
Offline
Joined: 2008-01-30

I am having the same problem, but SimpleUniverse.cleanup() does not fix it. When my app exits, it is left with the thread "J3D-Renderer-1" active, and as soon as I stop that thread my app exits.

Should the setDeamon() be used on the render thread? Why can't this thread be shut down with the rest of the universe and restarted if needed, and why is it SimpleUniverse.cleanup() and not .dispose()?

jeeky
Offline
Joined: 2003-06-15

Just to make sure I understand, is your problem that the rendering thread never dies or that you never reclaim you memory? IIUC, the rendering thread never dies but you don't need to worry about it. My experience was that gc'ing the scene took a very long time to happen. On the order of many, many seconds. As I recall, you also had to either explicitly invoke gc programmatically or via the NetBeans IDE gc button (if you are doing a memory analysis). The memory analysis tools in NetBeans are pretty useful for figuring out what's going on.

griffenjbs
Offline
Joined: 2008-01-30

My problem is the thread never dies, so the VM hangs. If I stop the renderer thread the VM exits normally. When my app has completed, there are still 7 other threads, AWT, Finalizer, etc, but none of them block the VM.

I'm using JRE 1.6.0_03-b05 from Sun on Windows XP, and Java3D 1.5.1

Message was edited by: griffenjbs

conzar
Offline
Joined: 2003-07-15

Don't mean to bring up a dead horse, but this horse still have some kick left in it for me.

I am running into the same problem. After I clean up everything, J3D-Renderer-1 thread is left dangling.

Here is what I do to clean up ... my case is different because I don't use SimpleUniverse ... so I don't have a nice SimpleUniverse.cleanup method.

* detach root BranchGroup from scene
* stop the view
* VirtualUniverse.removeAllLocales
* remove canvas from JFrame

== code for stopping the view ===
[code]
view.stopView();
view.stopBehaviorScheduler();
view.getCanvas3D(0).stopRenderer();
view.removeAllCanvas3Ds();
[/code]

I'm using JProfiler to track what threads are left running. I also notice that the memory associated with the J3D-Render thread is never released and so the memory double when I start a new instance.

Any thoughts?

Thanks

zesharp
Offline
Joined: 2006-12-21

>... J3D-Renderer-1 thread is left dangling.

As sad by Kevin above, this thread will remain alive, with minimal overhead. I think this is not a problem.

Check SimpleUniverse.cleanup() source code. It has some codes you probably miss , as free canvas3d off-screen buffer, universe.removeAllLocales(), Viewer.clearViewerMap(), Primitive clear geometry cache, etc.

Also, don't be shy to call System.gc() after releasing your Canvas3D. It's really helpful.

jeeky
Offline
Joined: 2003-06-15

Well, it appears that a simple call to universe.cleanup() does the trick (In a window listener or in dispose-it doesn't matter). The thing that was throwing me off was that I wasn't waiting a long time for the garbage collector to get all the old generations of garbage cleaned up. Once all of my windows were closed I could get the memory usage back down if I waited a long time and garbage collected or used the GC button in the NetBeans profiler. Eventually I got everything back.

Thanks for all the help on this.

jeeky
Offline
Joined: 2003-06-15

Can anyone confirm for me that this is the behavior that they get as well (i.e. memory is not being freed when the Java3D objects are supposed to go away)? I am still trying to determine if this is a bug in J3D or if I am doing something wrong.

Thanks!

stylertim
Offline
Joined: 2006-05-04

I'm having trobuel with this as well atm. When I remove my SimpleUniverse and Canvas3D the memory consumption actually rises by ~5MB... :)

- Thomas

jeeky
Offline
Joined: 2003-06-15

Posting of Main.java

[code]
package j3dheaptester;

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.JScrollPane;

/**
* @author Mark Bastian
*
*/
public class Main
{
public static void main(String[] args)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
}

SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JFrame frame = new JFrame("Leak Detection Application");
frame.setSize(450, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel panel = new JPanel();

JButton btnShow = new JButton("Show Frame");
btnShow.addActionListener(new ActionListener()
{

public void actionPerformed(ActionEvent e)
{
final JFrame frame = new J3DFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}

});

final JTextArea jta = new JTextArea();

Runnable updater = new Runnable()
{
public void run()
{
while(true)
{
String mem = showFreeMemory() + "\n";
jta.setText(mem + jta.getText());
//dumpMemoryInfo();
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

};

Thread memUpdater = new Thread(updater, "Memory Updater");
memUpdater.start();

panel.setLayout(new BorderLayout());
panel.add(btnShow, BorderLayout.SOUTH);
panel.add(new JScrollPane(jta), BorderLayout.CENTER);

JButton btnGC = new JButton("Run Garbage Collecter");
btnGC.addActionListener(new ActionListener()
{

public void actionPerformed(ActionEvent e)
{
Runtime rt = Runtime.getRuntime();
rt.gc();
}

});
panel.add(btnGC, BorderLayout.NORTH);

frame.add(panel);

frame.setVisible(true);
}
});
}

public static String showFreeMemory()
{
Runtime rt = Runtime.getRuntime();
long lFreeMem = rt.freeMemory();
long lMaxMem = rt.maxMemory();
long lTotalmem = rt.totalMemory();

String sMsg = "Current Memory Heap=" + (int)(lTotalmem/1000000)
+ "M Max Heap=" + (int)(lMaxMem/1000000) + "M "
+ "Free=" + (int)(lFreeMem/1000000)
+ "M Used=" + (int)((lTotalmem-lFreeMem)/1000000) + "M";

return sMsg;
}
}

[/code]

jeeky
Offline
Joined: 2003-06-15

TextureAppearance.java

[code]
/**
*
*/
package j3dheaptester;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.media.j3d.*;
import javax.vecmath.Color3f;

import com.sun.j3d.utils.image.TextureLoader;

/**
* @author Mark Bastian
*
*/
public class TextureAppearance extends Appearance
{
public TextureAppearance()
{
super();

float a = 0.8f;
float d = 0.5f;
Color3f ambient = new Color3f(a, a, a);
Color3f emissive = new Color3f(0.0f, 0.0f, 0.0f);
Color3f diffuse = new Color3f(d, d, d);
Color3f specular = new Color3f(d, d, d);
float shininess = 0.0f;
Material mat = new Material(ambient, emissive, diffuse, specular, shininess);
setMaterial(mat);
ambient = emissive = diffuse = specular = null;
mat = null;

TextureAttributes ta = new TextureAttributes();
ta.setTextureMode(TextureAttributes.MODULATE);
TextureUnitState tus = new TextureUnitState();
tus.setTextureAttributes(ta);
ta = null;

URL url = this.getClass().getResource("/j3dheaptester/image.png");
BufferedImage image = null;
try
{
image = ImageIO.read(url);
} catch(IOException e)
{
e.printStackTrace();
}
url = null;
image.flush();

TextureLoader loader = new TextureLoader(image);
Texture texture = loader.getTexture();
tus.setTexture(texture);
loader = null;

TextureUnitState[] textureUnitStates = { tus };
setTextureUnitState(textureUnitStates);
texture = null;
tus = null;
textureUnitStates = null;
image = null;
}
}
[/code]

jeeky
Offline
Joined: 2003-06-15

J3DFrame.java

[code]
/**
*
*/
package j3dheaptester;

import java.awt.*;
import java.util.*;

import javax.media.j3d.*;
import javax.swing.*;
import javax.vecmath.*;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;

/**
* @author Mark Bastian
*
*/
public class J3DFrame extends JFrame
{
/**
*
*/
private static final long serialVersionUID = 3481159812056854703L;
private SimpleUniverse universe;
private final BranchGroup scene;
private final TransformGroup tgRoot = new TransformGroup();
Canvas3D canvas;

public J3DFrame()
{
super("J3DFrame");

setSize(800, 600);

GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
canvas = new Canvas3D(config);

universe = new SimpleUniverse(canvas);
universe.getViewingPlatform().setNominalViewingTransform();

scene = createSceneGraph();

scene.addChild(tgRoot);

tgRoot.addChild(new Sphere(1.0f, Sphere.GENERATE_TEXTURE_COORDS | Sphere.GENERATE_NORMALS, new TextureAppearance()));

OrbitBehavior orbit = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_ALL);
BoundingSphere bounds =
new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 1.0);
orbit.setSchedulingBounds(bounds);
universe.getViewingPlatform().setViewPlatformBehavior(orbit);

universe.addBranchGraph(scene);

setLayout(new BorderLayout());
add(canvas, BorderLayout.CENTER);
}

private BranchGroup createSceneGraph()
{
BranchGroup bgRoot = new BranchGroup();

bgRoot.setCapability( Group.ALLOW_CHILDREN_EXTEND );

addLights(bgRoot);

return bgRoot;
}

public void addLights(BranchGroup bg)
{
Color3f color = new Color3f(1.0f, 1.0f, 1.0f);
Vector3f direction = new Vector3f(-1.0f, -1.0f, -1.0f);
DirectionalLight light = new DirectionalLight(color, direction);
BoundingSphere bounds =
new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 1.0);
light.setInfluencingBounds(bounds);
bg.addChild(light);
}

@Override
public void dispose()
{
universe.cleanup();
remove(canvas);
canvas = null;
universe = null;

System.out.println("Dispose is now being invoked.");
super.dispose();
}
}
[/code]

Message was edited by: jeeky
Added suggested changes (universe.cleanup()). I don't see any change in behavior.

Fixed typo (unverse.dispose() -> universe.cleanup())

Message was edited by: jeeky