As you may already be aware, there are some shortcomings to using reference counting as a garbage collection scheme. In fact, some people don't even consider reference counting to be true garbage collection, and reserve that term for algorithms such as "mark-and-sweep" garbage collection. The computer science literature on garbage collection is large and technical, and we won't get into it here. For our purposes it is enough to know that reference counting is a very simple form of garbage collection to implement, and it works fine in many situations. There are situations, however, in which reference counting cannot correctly detect and collect all "garbage", and you need to be aware of these.
The basic flaw with reference counting has to do with cyclical references. If object A contains a reference to object B and object B contains a reference to object A, then a cycle of references exists. A cycle would also exist, for example, if A referred to B, B referred to C, and C referred back to A. In cycles such as these, there is always a reference from within the cycle to every element in the cycle. Thus, even if none of the elements of the cycle has any remaining references, their reference count will never drop below one, and they can never be garbage collected. The entire cycle may be garbage, because there is no way to refer to any of these objects from a program, but because they all refer to each other, a reference-counting garbage collector will not be able to detect and free this unused memory.
This problem with cycles is the price that must be paid for a simple, lightweight, portable garbage collection scheme. The only way to prevent this problem is by manual intervention. If you create code in which A refers to B, B refers to C, and C refers to A, then you must be able to recognize that you've created a cycle, and take steps to force the cycle to be garbage collected when it is no longer needed.
When you know that the objects in your cycle are no longer in use, you can force them to be garbage collected by breaking the cycle. You can do this by picking one of the objects in the cycle and setting the property of it that refers to the next object to null. For example, suppose that A, B, and C are objects that each have a next property, and the value of this property is set so that these objects refer to each other and form a cycle. When these objects are no longer in use, you can break the cycle by setting A.next to null. This means that object B no longer has a reference from A, so its reference count can drop to zero and it can be garbage collected. Once it has been garbage collected, then it will no longer refer to C, so its reference count can drop to zero and it can be garbage collected. Once C is garbage collected, A can be garbage collected.
Note, of course, that none of this can happen if A, B, and C are stored in global variables in a window that is still open, because those variables A, B, and C still refer to the objects. If these were local variables in a function, and you broke their cycle before the function returned, then they could be garbage collected. But if they are stored in global variables, they will remain referenced until the window that contains them closes. In this case, if you want to force them to be garbage collected you must break the cycle and set the variables to null:
A.next = null; // break the cycle A = B = C = null; // remove the last remaining external references
First, if an object is created in one window, and then a reference to that object is stored in a variable in a second window, that object will be destroyed when the first window moves on to a new page, despite the fact that there is still an active reference to it from the other window. If this other window attempts to use this reference to the destroyed object, an error will result, possibly crashing the browser! This is an especially pernicious problem, because doing something as simple as assigning a string can cause this problem. Consider the following code:
newwin = window.open("", "temp_window"); newwin.defaultStatus = "temporary browser window".
The defaultStatus property is set to a string "owned" by the original window. If that window is closed, the string will be destroyed and the next reference to defaultStatus will go looking for a non-existing string.
It is possible to compensate, somewhat, for these memory management problems in Navigator 2.0. For the problem of memory not being released until the page is unloaded, the solution is simply to be careful about how much memory your scripts consume. If your page loops a lot or does a repetitive animation, look very carefully at the code that is executed over and over, and minimize the number of objects created on each iteration. Similarly, if you write a script that the user may use frequently without ever unloading, be sure to keep careful tabs on your memory usage.
Note that string manipulation is a big memory sink--each time you call a method on a string object, a new string object is generally created for the result. The same is true for string concatenation with the + operator.
For the problem of dangling references from one window to destroyed objects that were owned by another, one solution is to avoid programs that rely on inter-window references. Another solution is to be sure to make copies of all strings and other objects that are passed from one window to another. Suppose that in window 1, you want to set the defaultStatus property of window 2, as we saw earlier. If you do this directly with code in window 1, then window 2 will contain a reference to an object owned by window 1. But, if you call a function in window 2 to do the assignment, and make sure that the function makes a copy of the object, then the object assigned in window 2 will be owned by window 2. You could, for example, ensure that window 2 contains a definition of the following function:
With this function defined, you could then set the property from window 1 with a line like the following:
window2.set_string_property("defaultStatus", "temporary browser window");