Reference Counting


Jacl:

In Jacl, a handle is considered active as long as one TclObject holds a reference. When a Tcl variable is set to the handle for a Java object, the reference count of the TclObject that holds the Java object is incremented. When this variable is unset, the reference count is decremented.
# Ref count incremented to 1
set obj [java::new Object]

# Ref count incremented to 2
set copy $obj

# Ref count decremented to 1
unset copy

# Ref count decremented to 0, Java object freed
unset obj
A Java object handle can also be freed when the internal representation for a TclObject that holds a Java handle is changed. A Java object handle is created with an internal representation of type tcl.lang.ReflectObject.
# Ref count incremented to 1, ReflectObject internal rep
set obj [java::new Object]

# Ref count stays 1, converted to TclList internal rep, Java object freed
llength $obj
The attentive reader will deduce that there is actually another ref count managed by the ReflectObject, since in the first example the ref count of the TclObject was changed while in the second example it was not. This is in fact the case, although this implementation detail will likely not matter to most programmers. A ReflectObject has its own ref count to deal with the above case and to account for the fact that a single ReflectObject can be set as the internal representation for two different TclObject instances. The following example shows how this can happen.
# Ref count incremented to 1, ReflectObject internal rep
set obj [java::new Object]

# Copy just the string value of variable, ref count not changed
set copy [format %s $obj]

# Convert string to ReflectObject internal rep.
# Ref count for both TclObjects is 1.
java::isnull $copy

# Ref count decremented to 0
unset copy

# Ref count decremented to 0, Java object freed
unset obj

Tcl Blend:

In Tcl Blend, a handle is considered active as long as one TclObject holds a reference. The complication is that a C Tcl_Obj can have its internal rep set to point to a TclObject. When this happens, the ref count for the TclObject is incremented by 1. The important thing to note here is that there are actually 2 ref counts, one in the C Tcl_Obj, and another in the TclObject.
# TclObject created with ref count of 0.
# Tcl_Obj created to point at TclObject, increments TclObject.refCount to 1.
# Variable obj set to new Tcl_Obj, increments Tcl_Obj.refCOunt to 1.
set obj [java::new Object]

# Tcl_Obj.refCount incremented to 2.
# TclObject.refCount is not changed.
set copy $obj

# Tcl_Obj.refCount decremented to 1.
# TclObject.refCount is not changed.
unset copy

# Tcl_Obj.refCount decremented to 0, Tcl_Obj freed
# TclObject.refCount decremented to 0, Java object freed
unset obj
While the Tcl Blend implementation of ref counting is significantly more complex that Jacl, the end user should not see any differences between the two implementations at the script or Java code level.

One implementation detail worth mentioning is how native Tcl values are passed to a Java method. Two special internal reps, tcl.lang.CObject and tcl.lang.TclList, are used to implement this feature. The following Tcl command implemented in Java provides a simple example.

cat JS.java
import tcl.lang.*;
public class JS implements Command {
    public void cmdProc(Interp interp, TclObject[] objv)
        throws TclException
    {
        TclObject obj = objv[1];
        System.out.println("Java string is " + obj.toString());
    }
}

# Create new command in the Tcl interp
[java::getinterp] createCommand js [java::new JS]

set tstr "a Tcl string"

# Wrap existing Tcl_Obj in a TclObject with a CObject internal rep.
js $tstr
Java string is a Tcl string

This next example shows how an existing Tcl list object can be passed into Java. The important thing to note in this example is that the Java code operates directly on the native list from C.

cat JL.java
import tcl.lang.*;
public class JL implements Command {
    public void cmdProc(Interp interp, TclObject[] objv)
        throws TclException
    {
        TclObject obj = objv[1];
        interp.setResult( TclList.getLength(interp, obj) );
    }
}

# Create new command in the Tcl interp
[java::getinterp] createCommand jl [java::new JL]

set tlist [list 1 2 3 4]

# Wrap existing Tcl_Obj in a TclObject with a TclList internal rep.
jl $tlist
4

It is critically important to note that when a Tcl_Obj is wrapped in a TclObject, the reference count for the Tcl_Obj is not incremented. This is important because it must be possible for the Java garbage collector to cleanup a TclObject without having to invoke C code to decrement the ref count for a Tcl_Obj. The Java garbage collector is typically run in a separate native thread and it is not legal to operate on a Tcl_Obj from a thread other than the one it was created in.

The simple rule to remember when dealing with a TclObject in Java is that you must preserve() a reference you want to hold onto, and release() it when finished. The reference will keep Java from garbage collecting the TclObject, and incrementing the ref count will keep Tcl from releasing the Tcl_Obj.

cat Hold.java
import tcl.lang.*;
public class Hold implements Command {
    TclObject held = null;
    public void cmdProc(Interp interp, TclObject[] objv)
        throws TclException
    {
        if (held != null) {
            interp.setResult(held.toString());
            held.release();
            held = null;
        } else {
            held = objv[1];
            held.preserve();
        }
    }
}

# Create new command in the Tcl interp
[java::getinterp] createCommand hold [java::new Hold]

set hstr "valuable data"

# Wrap existing Tcl_Obj in a TclObject with a CObject internal rep.
# Increment ref count of Tcl_Obj to 2 by calling preserve().
hold $hstr

# Decrement ref count of Tcl_Obj to 1
unset hstr

# Set interp result to string value of held object
# and decrement the ref count of the Tcl_Obj to 0.
hold
valuable data

The above examples show how one can pass a Tcl_Obj into a Java method. The next example demonstrates how a Tcl_Obj can be created in Java code and returned to Tcl.

cat RL.java
import tcl.lang.*;
public class RL implements Command {
    public void cmdProc(Interp interp, TclObject[] objv)
        throws TclException
    {
        TclObject alist = TclList.newInstance();
        TclList.append(interp, alist, TclString.newInstance("ONE"));
        TclList.append(interp, alist, TclString.newInstance("TWO"));
        TclList.append(interp, alist, TclString.newInstance("THREE"));
        interp.setResult(alist);
    }
}

# Create new command in the Tcl interp
[java::getinterp] createCommand rl [java::new RL]

# Tcl_Obj allocated in Java set as the value of tlist.
# Ref count incremented to 1 by this set command.
set tlist [rl]

The examples above demonstrate the most simple and straightforward uses of a TclObject that acts as a wrapper around a native Tcl_Obj. In real code, there are additional complexities that could lead to a memory leak of the native Tcl_Obj. This next example shows how that could happen and how Tcl Blend fixes the problem.

cat Leak.java
import tcl.lang.*;
public class Leak implements Command {
    public void cmdProc(Interp interp, TclObject[] objv)
        throws TclException
    {
        TclObject obj = objv[1];
        double dval = TclDouble.get(interp, obj);
        int len = TclList.getLength(interp, obj);
        interp.setResult("Double -> " + dval + " llength -> " + len);
    }
}

# Create new command in the Tcl interp
[java::getinterp] createCommand leak [java::new Leak]

set v 1.0

leak $v
Double -> 1.0 llength -> 1
At first glance, it looks as though all is well in the leak command above. What actually happens is that the TclList.getLength() call creates a new Tcl_Obj that would not be deallocated. This leak could happen because the TclDouble.get() call converts the passed in TclObject from a tcl.lang.CObject internal rep to a tcl.lang.TclDouble internal rep. When that happens, the Tcl_Obj stored in the tcl.lang.CObject is discarded. When the object is converted back to a list type via the TclList.getLength() call, there is no existing Tcl_Obj to operate on, so a new one is allocated with a ref count of 0. The TclObject with a newly allocated Tcl_Obj inside of it is not used again, which could lead to a memory leak.

Tcl Blend includes a feature that automatically deals with this sort of memory leak. When a Tcl command implemented in Java returns, Tcl Blend will check to make sure that a native Tcl_Obj allocated in the Java method is deallocated. Such a Tcl_Obj will only be deallocated if it has a ref count of 0, meaning its ref count was never incremented or decremented in the Java method. This feature will cleanup those Tcl_Obj pointers that would have been leaked, without effecting a TclObject that had its ref count changed by preseve() or release().

Copyright © 1997-1998 Sun Microsystems, Inc.