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 is my class code
Option Explicit Private Sub class_initialize() Debug.Print "initializing.." End Sub Private Sub Class_Terminate() Debug.Print "terminating" End Sub
Here is 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 is 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 bRefPrivate 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 is 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 publicDim 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 is the cBidule classOption 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 PropertyThe 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 back link 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 Subthe 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 classPrivate 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.
For help and more information join our forum,follow the blog or follow me on twitter .