Objects and the garbage collector

When I first started playing around with VBA, everything I read emphasized how important it was that when you initialized an object, you remembered to set it to nothing afterwards in order to 'release the memory'.

I did that for a while, but soon realized that this was not exactly correct.

The Garbage Collector

As in many platforms, VBA has a mysterious garbage collector whose function is to return memory to the pool when it is no longer being referenced. The key here is 'referenced'. When you create a reference like

set b = new cBidule

then the new instance of a cBidule class becomes referenced by the variable b. It follows then if you remove the reference to it by 

set b = Nothing

then that reference is removed and it then qualifies for the garbage collector's attention, but how to test?

Class_Terminate and Class_Initialize

VBA provides a special terminate and initialize function. When specified in your class, this will be called when it is created or terminated. If removing all references to an object destroys it, then we should see the class terminate being executed. 

Here's my class code, 
Option Explicit
Private Sub class_initialize()
    Debug.Print "initializing.."
End Sub
Private Sub Class_Terminate()
    Debug.Print "terminating"
End Sub

Here's a test
Private Sub testLeakMain()
    Dim bMain As cBidule
    Debug.Print "starting.."
    Set bMain = New cBidule
    Set bMain = Nothing
    Debug.Print "done.."
End Sub

.. and here's the result
starting..
initializing..
terminating
done..

Perfect - exactly as expected. Setting bMain to nothing cause the object it references to be destroyed.

Multiple references

So let's say we have multiple references to the same object. In this case we reference the object with both bMain and bRef
Private Sub testLeakMain()
    Dim bMain As cBidule, bRef As cBidule
    Debug.Print "starting.."
    Set bMain = New cBidule
    Set bRef = bMain
    Set bMain = Nothing
    Debug.Print "done.."
End Sub

Here's the result
starting..
initializing..
done..
terminating

Not quite the same result. This shows that the object is not released following setting bMain to nothing, since it is still being referenced by bRef. However the object is still released - when bMain goes out of scope.  This is an important finding. It means that set x = nothing is irrelevant unless you need to recover the memory inside the same module. 

The object memory will always be released when all variables accessing them have gone out of scope. There is no 'specialness' about setting a reference to nothing, except to accelerate the reduction of the reference count.

Public variables

As discussed in Understanding Scope a public variable will be accessible outside the scope of a function. This means that a reference by a public variable will not go out of scope, and therefore the object memory will not be released

Here's the same thing, except our references are public
Dim bMain As cBidule, bRef As cBidule
Private Sub testLeakMain()
    Debug.Print "starting.."
    Set bMain = New cBidule
    Set bRef = bMain
    Set bMain = Nothing
    Debug.Print "done.."
End Sub

and the result shows that the object is never terminated, since the bRef object is public
starting..
initializing..
done..

Now if we make the bRef variable private, but leave the bMain public, since we set bMain to nothing and bRef will go out of scope automatically, we should see a class termination.
Dim bMain As cBidule
Private Sub testLeakMain()
    Dim bRef As cBidule
    Debug.Print "starting.."
    Set bMain = New cBidule
    Set bRef = bMain
    Set bMain = Nothing
    Debug.Print "done.."
End Sub

and that's exactly what happens
starting..
initializing..
done..
terminating

Memory Leaks with custom classes

The garbage collector can be pretty basic. Lets say that we have two objects that reference each other.

here's the cBidule class
Option Explicit
Private pChildren As Collection
Private Sub class_initialize()
    Set pChildren = New Collection
    Debug.Print "initializing bidule"
End Sub
Private Sub Class_Terminate()
    Debug.Print "terminating bidule"
End Sub
Public Property Get children() As Collection
    Set children = pChildren
End Property

and the cWidget class
Option Explicit
Private pParent As cBidule
Private Sub class_initialize()
    Debug.Print "initializing widget"
End Sub
Private Sub Class_Terminate()
    Debug.Print "terminating widget"
End Sub
Public Property Set parent(p As cBidule)
    Set pParent = p
End Property

The cBidule class is going to contain a collection of cWidgets. 

here's the test
Private Sub testLeakMain()
    Dim bBid As cBidule, bWid As cWidget
    Debug.Print "starting.."
    Set bBid = New cBidule
    Set bWid = New cWidget
    bBid.children.Add bWid
    Debug.Print "done.."
End Sub

and it works perfectly, terminating both instances as they go out of scope
starting..
initializing bidule
initializing widget
done..
terminating bidule
terminating widget

But, let's say we also create a backlink to the parent bidule in each widget. This would be a pretty common thing to do.
Private Sub testLeakMain()
    Dim bBid As cBidule, bWid As cWidget
    Debug.Print "starting.."
    Set bBid = New cBidule
    Set bWid = New cWidget
    Set bWid.parent = bBid
    bBid.children.Add bWid
    Debug.Print "done.."
End Sub

what..?? they don't terminate on going out of scope
starting..
initializing bidule
initializing widget
done..

That's because the garbage collector still thinks there are references to each object - even though both have gone out of scope.

That's pretty amazing. So let's revisit whether setting things to nothing is of any value. 
Private Sub testLeakMain()
    Dim bBid As cBidule, bWid As cWidget
    Debug.Print "starting.."
    Set bBid = New cBidule
    Set bWid = New cWidget
    Set bWid.parent = bBid
    bBid.children.Add bWid
    Set bWid = Nothing
    Set bBid = Nothing
    Debug.Print "done.."
End Sub

the result - as we've already established, setting to Nothing makes no difference.
starting..
initializing bidule
initializing widget
done..

The need for a tearDown method

The lesson here is that wherever there are backlinks between objects, you need to create a tearDown method to break those links, otherwise you will never release up the memory. 

the updated bidule class
Private pChildren As Collection
Private Sub class_initialize()
    Set pChildren = New Collection
    Debug.Print "initializing bidule"
End Sub
Private Sub Class_Terminate()
    Debug.Print "terminating bidule"
End Sub
Public Property Get children() As Collection
    Set children = pChildren
End Property
Public Sub tearDown()
    Dim w As cWidget
    For Each w In children
        w.tearDown
    Next w
End Sub

the updated widget class
Private pParent As cBidule
Private Sub class_initialize()
    Debug.Print "initializing widget"
End Sub
Private Sub Class_Terminate()
    Debug.Print "terminating widget"
End Sub
Public Property Set parent(p As cBidule)
    Set pParent = p
End Property
Public Sub tearDown()
    Set pParent = Nothing
End Sub

and the test
Private Sub testLeakMain()
    Dim bBid As cBidule, bWid As cWidget
    Debug.Print "starting.."
    Set bBid = New cBidule
    Set bWid = New cWidget
    Set bWid.parent = bBid
    bBid.children.Add bWid
    bBid.tearDown
    Debug.Print "done.."
End Sub

now successfully releases the memory
starting..
initializing bidule
initializing widget
done..
terminating bidule
terminating widget

Summary

  • Setting things to Nothing is of no special value except as one of the strategies to reduce object reference counts to zero. Letting the reference go out of scope is perfectly fine too.
  • When you have a custom class, be careful of double linkbacks. This is very common in a parent/children type structure. You should always consider a tearDown() method when creating a custom class if there is a chance of these kind of references. You cannot rely on the garbage collector to be able to terminate these kind of objects when they go out of scope.

Badges iframe plugin

See How to set up your own badge environment if you want to award badges on your own site. Click on the badges on the right to see how it works.





For help and more information join our forum,follow the blog or follow me on twitter .




Comments