Skip to main content

Java3D Memory Leak

4 replies [Last post]
ralphld
Offline
Joined: 2010-08-02

I'm getting an OutOfMemoryError for Java heap space. I know I can
increase memory allocation with "-Xms#" etc. but that just delays the
error. I believe this is the root cause of the problem.

Node.java and NodeRetained.java have 'Strong References' to one another
so that they (and all their descendants) remain in memory after I have
tried to delete them. I've verified this with jhat and jmap
(hopefully).

I create scene graphs (streets, intersections, lights, etc.) on the
fly when a user opens a 'map', and I need to be able to wipe them out
when the user closes one map and opens another.

In the same vein, I create vehicles when they enter the map and delete
them when they leave, so my memory use increases as long as the 3D
display is open and the simulator is running. I also have a 2D display
which has no memory leakage, so I'm reasonably sure that Java3D is the
source of the problem.

I 'm doing the following (some of which may be overkill) but nothing
seems to help.

// clear hanging memory
if (canvas != null) {
if (canvas.isOffScreen()) canvas.setOffScreenBuffer(null);
canvas.getView().removeAllCanvas3Ds();
locale.removeBranchGraph(traffic); // ??? useful
locale.getVirtualUniverse().removeAllLocales();

traffic.detach(); // the global BranchGroup
traffic = null;
canvas.setLocale(null);
canvas = null;
System.gc();
}

I'm thinking of submitting a request to make the links between Node.java
and NodeRetained.java into WeakReferences, but that would change the
'signatures' of the objects, so it couldn't be implemented without a
major version change to Java3D (I believe).

Are there any other alternatives?

Thanks,
Ralph

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
interactivemesh
Offline
Joined: 2006-06-07

Hi,

while disposing of universes we have to be careful not to stop the internal Java 3D engine loop. This might happen when the JFrame holding a Canvas3D is minimized, or the Canvas3D is removed from its GUI-parent, or the View holds no Canvas3D, or no ViewPlatform is attached to the View. All later method calls for detaching a BranchGroup will not be fully executed!

In one of my applications following approach works fine:

- Firstly, detach only those BranchGroups where all Shape3Ds are located while the universe is still live and running.
- Secondly, at least one frame later (!!), remove all Canvas3D from their Views and detach all ViewPlatforms.
- Thirdly, clean the GUI

The implementation is based on a selfcalling Behavior, which doesn't live in the Shape3Ds' BranchGroups (!!), and a callback method as shown in this simplified code snippet:

[code]
class MyClass {

// Starts disposing of the universe
// Called from GUI on EDT (Event Dispatch Thread)
void disposeUniverse() {
universeDisposeBehavior.clearUniverse();
}

// Called from behavior on a Java 3D thread
void disposeCallback() {
// Leave the Java 3D thread for the EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {

view.removeAllCanvas3Ds();
view.attachViewPlatform(null);

// Clean the GUI

System.gc(); // Just to prevent GC from falling asleep
}
});
}

// Inner class
class UniverseDisposeBehavior extends Behavior {

static final int CLEAR_SCENE = 1;
static final int CLEAR_UNIVERSE = 2;
WakeupOnBehaviorPost scenePost = new WakeupOnBehaviorPost(this, CLEAR_SCENE);
WakeupOnBehaviorPost universePost = new WakeupOnBehaviorPost(this, CLEAR_UNIVERSE);

UniverseDisposeBehavior() { }

void clearUniverse() {
// Process me
this.postId(CLEAR_SCENE);
}

@Override
public void initialize() {
wakeupOn(scenePost);
}
@Override
public void processStimulus(Enumeration criteria) {
while (criteria.hasMoreElements()) {
Object wakeup = criteria.nextElement();
if (wakeup instanceof WakeupOnBehaviorPost) {

// First frame
if (((WakeupOnBehaviorPost)wakeup).getPostId() == CLEAR_SCENE) {

// All Shape3Ds are in 'sceneBranch'
sceneBranch.detach();
sceneBranch.removeAllChildren(); // really needed ??

// Process me again in one of the next frames
this.postId(CLEAR_UNIVERSE);
wakeupOn(universePost);
}
// Next frame
else if (((WakeupOnBehaviorPost)wakeup).getPostId() == CLEAR_UNIVERSE) {
disposeCallback();
}
}
}
}
}
}
[/code]
The increase of the memory footprint after repeatedly creating and disposing of universes with millions of triangles is negligible.

August

ralphld
Offline
Joined: 2010-08-02

Thanks, I've gotten it running, but I'm not seeing a difference yet. I'm assuming that that is my fault.

I do have one question. Where are you attaching your UniverseDisposeBehavior Behavior to your universe?
I added mine to the view platform BranchGroup, but that still seems clunky.

Ralph

interactivemesh
Offline
Joined: 2006-06-07

The top level BranchGroups in my programs are typically structured as follows and common Behaviors are added to the environmentBranch.

[code]
virtualUniverse
- locale
- sceneBranch
- (TransformGroup, Shape3D, etc.)
- viewingBranch
- viewingTG
- viewPlatform
- environmentBranch
- background
- universeDisposeBehavior ( setSchedulingBounds(Bounds region) ! )
[/code]
The 'Heap Memory Usage' can be watched with the Java Monitoring & Management Console [b]JConsole[/b] to be found at '../jdk1.6/bin/jconsole.exe'. See also http://download-llnw.oracle.com/javase/6/docs/technotes/guides/managemen...

August

ralphld
Offline
Joined: 2010-08-02

Thanks, I've going to rearrange my BranchGroups a bit to (hopefully) simplify detaching all of them.

FWIW, this is a JPEG of my app, I had a BranchGroup for the static background (with subgraphs for assemblies like the traffic lights), a BranchGroup for the vehicles, and a BranchGroup for the view and other non-shape3D stuff.

http://minitraff.sourceforge.net/screenshots.html#grid3

I took a quick look at JConsole, It looks like it is much more accurate on heap growth than top (see below). I need to look at the reference you posted to see if JConsole can track individual objects (like a BranchGroup).

I'm usually fairly low tech. I've been using the aliases and the script below (under Linux) because I can drill down to an object and see where it is referenced (with jhat). I'm going through now and finding specific retained references that I can detach.

# get system view of memory footprint
alias jtop='top -p $(ps -C java -o pid=)'

# get memory by object
alias jm='jmap -histo:live $(ps -C java -o pid=) | o'

and

#!/bin/sh
## jdump - browse java object references with lynx
##
cd /home/ralph/
rm -f plug
jmap -dump:live,file=plug $(ps -C java -o pid=)
sleep 3
jhat plug &
setterm -bold on
setterm -foreground red
echo "Hit when 'Server is ready'"; read temp
setterm -bold off
setterm -foreground cyan
echo "running";
lynx http://localhost:7000
kill -9 $(ps -C jhat -o pid=)

Thanks again,
Ralph