I did that for a while, but soon realized that this was not exactly correct. The Garbage CollectorAs 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_InitializeVBA 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 referencesSo 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 variablesAs 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 classesThe 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 methodThe 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
For help and more information join our forum,follow the blog or follow me on twitter .
|
Services > Desktop Liberation - the definitive resource for Google Apps Script and Microsoft Office automation > Get Started Snippets > Understanding Scope >