I got a note from Mukund, who is investigating a memory leak problem. 

Hi Jessica, Is it true that you cannot explicitly destroy objects in VB.NET?

Mukund, the truth is that the CLR both giveths and takeths away the memory for you. 
Once a whole bunch of junk gathers, the CLR runs through all the allocated objects and figures out which ones can no longer be used.  This is determined by walking through the heap of objects and finding out who is no longer being referenced.  This list is then automatically cleaned up, and the memory may be freed or re-used by the CLR to allocate a different object.

There are several articles out there on this topic that cover this much better than I ever could:

Instead of diving into GC nittygritty, I think I might be able to share some of my battle scars from debugging my own memory leaks. 

1. Leak of GDI objects or USER objects

This is most easily diagnosed by bringing up the task manager, switch to processes. View->Select Columns and tick off USER objects and GDI objects. This should show you the live count of how many handles you have out.  You should remain at a steady state.

If you are not seeing a steady state, consider reading up on the Dispose method - it could be someone is not cleaning up Pens, Brushes, modal Forms correctly.

2. Leak in p/invoke code

This one is hard to figure out, but Raymond Chen has a good suggestion for figuring out what kind of thing is actually leaking: make the thing leak quite a bit, then load up the memory window, pick random spots in memory and see whats scribbled inside.

Obviously you'll also want to review any calls to Marshal.Alloc* to make sure you're also freeing your memory.

3. Leak of managed objects

Make sure you know who you're giving your object out to AND how long they're planning to stick around. If you're stuffing your object in a static (or shared) list, it should be removed at some point in time, as that shared list will hold open the lifetime of your object. 

Same goes for non static stuff that will be around for the lifetime of your application (like a form).  If the Form is hanging onto the object, the object is not going to go away until everyone lets go of the Form.

One other place to look is places where you're hooking onto static events.   (I've mentioned this one before.)  The most common culprits are SystemEvents.UserPreferenceChanged and Application.Exit, Application.Idle etc.  If you're hooking onto these events, remember to unhook when you're ready for cleanup, otherwise the Application and/or SystemEvents class will keep your object alive.

Most often I wind up debugging these kinds of issues using strike commands that allow you to see what's really going on in the garbage collector.

!load sos
extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded

!dumpheap -stat
PDB symbol for mscorwks.dll not loaded
total 8662 objects
      MT    Count    TotalSize Class Name
7b477ae0        1           12 System.Windows.Forms.AutoScaleMode
7b4779b8        1           12 System.Windows.Forms.OSFeature
7b475ca8        1           12 System.Windows.Forms.FormCollection
7a75661c        1           12 System.Diagnostics.TraceListenerCollection
7a753394        1           12 System.Diagnostics.TraceOptions
7a7488a8        1           12 System.Collections.Specialized.StringDictionary
7912b908        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
79128f18        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.Int16, mscorlib]]
0091593c        1          324 WindowsApplication15.Form1

Use the MT (method table) number from the output of dumpheap -stat to get the list of actual addresses for the objects.  In this case there's only one Form1, so we'll only see one address listed.

!dumpheap -MT 0091593c
 Address       MT     Size
012b2e20 0091593c      324    
total 1 objects
      MT    Count    TotalSize Class Name
0091593c        1          324 WindowsApplication15.Form1
Total 1 objects

From here you can use the address to see who is hanging onto it

!gcroot 012b2e20

To find out more information about the particular object at a memory address, you can use dumpobj.

!dumpobj 012b2e20

When you do the GCRoot, keep a lookout for PINNED roots, this means that the root object isnt going to go away, therefore your leaf object will stick around.

You may also be able to use the CLR profiler to help track down what kinds of objects are being created and how long they're hanging around in the garbage collector - but it doesn't show you who is holding onto the object you care about.

4. Leak of GCHandles

Keep a watch out for anything that manually does a GCHandle.Alloc.  If this code can be written in a different (more robust) way by using some of the other wrappers that the CLR provides (like WeakReference) or by holding something in a member variable, this will prevent the errors that may come from forgetting to free the handle.

You can detect GCHandle leaks through the GCRoot and GCHandles and GCHandleLeaks commands in strike.