3.10. References and garbage collection (Java only)

Scene graph nodes are created in the usual fashion, and the lifetime of each node is managed by the Java Virtual Machine. However, when an application removes the scene graph or a part of it, it may be necessary to immediately reclaim all the memory used by the scene graph without waiting for the garbage collector to do it. This is often the case for large scene graphs. The following paragraphs explain how to reclaim memory used by the Open Inventor scene graph when the application does not want to wait for the garbage collector

Note: This manual memory management might be necessary because each OpenInventor Java SoNode instance is a wrapper object that encapsulates a native C++ SoNode object. As the Java wrapper object does not know the memory size of the native C++ instance, the garbage collector is not able to correctly evaluate the total cost of the Java instance. The garbage collector might therefore delay the collection of the instance and the deletion of the wrapped C++ instance.

In order to reclaim immediately a node’s memory, the node must be completely unreferenced by Open Inventor. Remember that a node N can be referenced in the following ways, among others:

  • by inserting N in a group: ex SoGroup.addChild(N)

  • by setting N in an Open Inventor single field: ex SoSFNode.setValue(N);

  • by inserting N in an Open Inventor multiple field: ex SoMFNode.set1Value(N);

  • by defining N as the scene graph of a viewer: ex SwRenderArea.setSceneGraph(N)

  • by inserting N in a path: ex SoPath.regular.append(N), setHead(N)

Therefore, before trying to reclaim a node’s memory, the application must dereference the node in one of the following ways:

  • SoGroup.removeChild(N)

  • SwRenderArea.setSceneGraph(null);

  • SoPath.regular.pop(), SoPath.regular.truncate(...) etc...

Warning: When a SoSearchAction is applied, an SoPath is built. This action indirectly makes additional references to the found nodes.

Once a node has no more references, its internal memory can be immediately freed when its dispose() method is called. For example, a common case occurs when an application wants to reclaim the memory associated with a scene graph immediately after closing an Open Inventor render area. For example:

void onClose(SwRenderArea renderArea) {
  SoNode root = renderArea.getSceneGraph();
  renderArea.setSceneGraph(null);
  if (root.isDisposable())
  root.dispose();
}

Using the isDisposable() method

All Open Inventor classes that have the dispose mechanism implement the interface com.openinventor.inventor.SafeDisposable. The method isDisposable() returns true if the Open Inventor object can be disposed safely; in fact it will be true only if the object is no longer referenced by Open Inventor. A generic example of disposing an Open Inventor object might be:

void safeDisposeInventorObject(SafeDisposable oivObj) {
  // remove all ref to oivObj
  ...
  if (oivObj.isDisposable())
    oivObj.dispose();
}

If the application tries to dispose an object without testing isDisposable() and the object is still referenced by Open Inventor, then the exception java.lang.IllegalStateException will be thrown. Therefore, an alternate way to dispose might be:

void safeDisposeInventorObject(SafeDisposable oivObj) {
  // remove all ref to oivObj
  ...
  try {
    oivObj.dispose();
  } catch (java.lang.IllegalStateException exc) {
  }
}

Recursive dispose

If dispose() is applied to a group node instance (SoGroup or any derived class), the method traverses all the group’s children recursively. Thus, the whole scene graph rooted at this group is disposed. It is not necessary for an application to traverse explicitly all the children, remove the children, and dispose them. The following pictures explain what append when sroot.dispose() is called.

Initial scene graph

Figure 3.16.  Initial scene graph



Derefence and dispose sroot

Figure 3.17.  Derefence and dispose sroot



The scene graph contains just root: all others nodes are disposed

Figure 3.18.  The scene graph contains just root: all others nodes are disposed



When the scene graph shares some internal nodes with another scene graph, the dispose() method will only dispose the nodes that are not shared. The following pictures explain what append when sroot1.dispose() is called.

Initial scene graph

Figure 3.19.  Initial scene graph



Derefence and dispose sroot1

Figure 3.20.  Derefence and dispose sroot1



Unshared children of sroot1 are disposed

Figure 3.21.  Unshared children of sroot1 are disposed



Limitation 1:

This implicit recursive behavior does not apply for any Inventor node that refers to some other nodes by a field SoSFNode or SoMFNode. Example:

SoVertexProperty myVertexProperty = new SoVertexProperty();
SoIndexedFaceSet myShape = new SoIndexedFaceSet();
myShape.vertexProperty.setValue(myVertexProperty);

if (myShape.isDisposable())
  myShape.dispose();

In this example, even if myShape is disposable, Open Inventor does not ensure that the node myVertexProperty is disposed; that will happen when the JVM can collect the myVertexProperty instance. The following code could be used as workaround:

void disposeIndexedShape(SoIndexedFaceSet myShape)
{
  SoVertexProperty vertexProperty = (SoVertexProperty)myShape.vertexProperty.getValue();
  if (myShape.isDisposable())
    myShape.dispose();
  if (vertexProperty.isDisposable())
    vertexProperty.dispose();
}

The same limitation exists for field containers such as SoSFFieldContainer and SoMFFieldContainer, and also for any SoVolumeReader set by an SoVolumeData. The volume data keeps an instance of this reader, but when disposing the volume data, its volume reader may not be disposed until the runtime collects it. For example:

SoVolumeData volumeData = new SoVolumeData();
SoVRMemoryReader volumeReader = new SoVRMemoryReader();
volumeData.setReader(volumeReader);
if (volumeData.isDisposable())
  volumeData.dispose();

In this example, after volumeData is disposed, volumeReader may be not disposed.

Limitation 2:

SoPath also has the same kind of limitation. If the application adds some Inventor nodes to an SoPath, those nodes may not be disposed when this path is disposed, even if they are not referenced elsewhere. For example:

SoGroup group1 = new SoGroup();
SoGroup group2 = new SoGroup();
SoPath path = new SoPath();
path.regular.append(group1);
path.regular.append(group2);
if (path.isDisposable())
  path.dispose();

In this example, after path is disposed, group1 and group2 may be not disposed.

When an SoSearchAction is successfully applied, the getPath() method returns an SoPath instance containing an ordered list of nodes from the searched scene graph. As the action keeps a reference to the returned path, the path and also the nodes in the path cannot be disposed before the action. Unfortunately, the SoAction classes do not implement the dispose() method. Therefore, the path and its nodes cannot be disposed before the runtime collects the action instance.

SoGroup root = new SoGroup();
SoCone cone = new SoCone();
root.addChild(cone);

SoSearchAction searchAction = new SoSearchAction();
searchAction.setNodeClass(SoCone.class);
searchAction.apply(root);
SoPath path = searchAction.getPath();

if (root.isDisposable())
  root.dispose();

In this example, the root is not disposable because the path maintains a reference to root. Furthermore the path is also not disposable because the action maintains a reference to the path. The action must be collected before the path and root can be disposed.

In order to dispose the root before the action is collected, the action must remove its references to any path. It can be done by calling reset() like in the following example:

SoGroup root = new SoGroup();
SoCone cone = new SoCone();
root.addChild(cone);

SoSearchAction searchAction = new SoSearchAction();
searchAction.setNodeClass(SoCone.class);
searchAction.apply(root);
SoPath path = searchAction.getPath();

// remove the reference to the path 
searchAction.reset();

// dispose the path in order to let the root be disposable
if (path.isDisposable())
  path.dispose();

if (root.isDisposable())
  root.dispose();

The same constraint exists when using an SoRayPickAction. When a picking occurs the action handles a list of SoPickedPoint. As each SoPickedPoint handles also an SoPath, the method clearPickedPointList() must be called before trying to dispose the root:

SoGroup root = new SoGroup();
SoCone cone = new SoCone();
root.addChild(cone);

SoRayPickAction pickAction = new SoRayPickAction(new SbViewportRegion());
pickAction.setRay(new SbVec3f(0, 0, 0), new SbVec3f(0.5f, 0.5f, 0.5f));
pickAction.apply(root);
SoPickedPoint pickedPoint = pickAction.getPickedPoint();
SoPath pickedPath = pickedPoint.getPath();

// removes the reference to the picked point list
pickAction.clearPickedPointList();

// dispose the path in order to let the root be disposable
if (pickedPath.isDisposable())
  pickedPath.dispose();

if (root.isDisposable())
  root.dispose();