Avoiding Memory Leaks in Objects
In this article, I will discuss the potential memory leak that can occur if object references aren't handled in a proper way.
Author: Jens G. Balchen

Introduction

In modern Visual Basic, objects play a vital role. Since version 4.0, VB developers have had the opportunity to create and destroy objects at their own will. To understand why objects can and will cause memory leaks in Visual Basic, you have to understand how they work.

Creating the Object

Whenever you create an object in Visual Basic, you actually create two things -- an object, and a pointer (called an object reference). "VB does not use pointers", you might say, but that is not true. "VB does not let you manipulate pointers" is more precise. Behind the scenes, VB still makes extensive use of pointers.

The following code will create an object and a reference, both referred to as A:


Dim A As Class1

   Set A = New Class1

To VB, this code reads something like this:

Create an object reference (pointer) called A to an object of type Class1
Create a new instance of Class1 and make A point to this instance.

If at some point you were to write

Dim B As Class1

   Set B = A

what would have happened, is

Create an object reference (pointer) called B to an object of type Class1
Make B point to the same object instance as A.

If you modify B, A will have been modified as well, which is quite obvious since they refer to the same object.

Destroying the Object

To destroy an object in VB, you set it to Nothing. But wait a minute. If all we ever use are object pointers, how can we set the object itself to Nothing? The answer is: We can't.

When we set a reference to Nothing, something called the garbage collector kicks in. This little piece of software tries to determine whether or not an object should be destroyed. There are many ways to implement a garbage collector, but Visual Basic uses what is called the reference count method.

As I said earlier, all we ever use are references. When I execute the following code, I will not touch the object directly; instead, I will reset and destroy the object reference.


Dim A As Class1

   Set A = New Class1
   Set A = Nothing
   
When VB interprets the last line, it will remove the reference A. At this point, if the object has no more references, the garbage collector will destroy the object and deallocate all its resources. If any other references point to the same object, the object will not be destroyed.

Dim A As Class1
Dim B As Class1

   Set A = New Class1
   Set B = A
   Set A = Nothing
   
At this point, the object will still be alive, since B holds a reference to it.

Fooling the Garbage Collector

The garbage collector is not really very smart, and it cannot keep up with the incredible pace at which humans produce errors. There is an easy way to trick the garbage collector into thinking an object should be kept alive indefinetely. This can best be illustrated with these two classes, Class1 and Class2.

' Class1 code
Option Explicit

Public A As Class2

Public Sub Class_Initialize()

   Set A = New Class2
   Set A.B = Me

End Sub

' Class2 code
Option Explicit

Public B As Class1

When an instance of Class1 is created, it will create an instance of Class2. This way, Class1 keeps a reference to Class2. But it will also set a reference in Class2... to itself. This way, Class1 referes to Class2, and Class2 referes to Class1. This is what we call circular references.

If I were to create and destroy an object of Class1, this is what I would do:


Dim MyObject As Class1

   Set MyObject = New Class1
   ...
   Set MyObject = Nothing
   
Behind the scenes, Class1 will have created another object, and set up a circular reference. When I destroy MyObject, I would expect the garbage collector to clean up. However, the garbage collector is left with the following situation:
  1. Object Class2 points to object Class1, so Class1 can't be destroyed even if MyObject is destroyed.
  2. Object Class1 points to object Class2, so Class2 can't be destroyed.
Oops.

Producing a Memory Leak

Modifying the above classes, it is very easy to produce VB code that will leak memory like crazy. The following code can be pasted into two classes and a form.

' Class1 code
Option Explicit

Private A As New Class2
Private Str As String
Private Sub Class_Initialize()

   Set A.B = Me
   
   ' Allocate lots of memory
   Str = Space(1024 ^ 2)

End Sub

' Class2 code
Option Explicit

Public B As Class1

' Form code
Private Sub Command1_Click()

Dim A As Class1

   Do
      ' Will allocate 1 MB of memory.
      Set A = New Class1
      ' Will leak 1 MB of memory.
      Set A = Nothing
      DoEvents
   Loop

End Sub

On my development machine, I have 256 MB RAM and 256 MB virtual memory. The following image is taken from Task Manager after this code had been running for a while, and then stopped.

As you can see, the application is very efficient at leaking memory.

Avoiding the Problem

The obvious solution to this problem is avoiding circular references. However, this is not always a viable solution, since sometimes, circular references actually provide very useful functionality.

The best way to prevent this problem from arising is keeping a clear mind and adopting a structured way of working with objects. The following procedure could be inserted into any class, and should be modified to fit the needs of that particular class:


Public Sub Deallocate()

   ' Deallocate any object references and make sure they have a chance to
   ' deallocate theirs.
	
   A.Deallocate
   Set A = Nothing

   B.Deallocate
   Set B = Nothing
	
   ' ... etc...

End Sub

A more generic solution

If you want to provide some help to yourself and other programmers you work with, you could create an interface for this type of functionality.

Step 1: A template

Considering the previous Deallocate function, one could create a class called SafeObject that would contain this function.

' SafeObject code

Public Sub Deallocate()

End Sub

Step 2: Using the template

Other objects could implement this interface, like this:

' Class1 code
Option Explicit
Implements SafeObject

Private Sub SafeObject_Deallocate()

   ' Class1's special deallocation procedure.	

End Sub

Step 3: New ways of destroying

The last step is to create a function in a module:

' mdlSafeObject code

Public Sub DestroyObject(o As Object)

Dim SafeO As SafeObject

   ' If this is a SafeObject, call Deallocate() first.
   If TypeOf o Is SafeObject Then 
      ' This is necessary to make VB recognise o as a SafeObject
      Set SafeO = o
      SafeO.Deallocate
   End If

   Set o = Nothing

End Sub

' Class1 code

Public B As Class2
Public C As Class3

Private Sub SafeObject_Deallocate()

   ' Destroy all references.
   DestroyObject B
   DestroyObject C

End Sub

If you always use the SafeObject interface, and always use DestroyObject instead of Nothing, all objects will at least have the ability to perform a safe deallocation of their references.