I’m chasing a memory leak at the moment by commenting out code. Is there a better way to find the problematic code?
- Checking if destructors are being called when they should (by printing with System.DebugLog to the console).
- For UI elements, checking them after closing in the debugger’s runtime object list (this only works if the project is small and you don’t use introspection which “pollutes” this list).
In general I think it depends very strongly on the architecture of the application. The more decoupled the objects are and the less global state there is, the easier it seems to me to get the part of the code leading to a memory leak.
There is no UI involved. The memory leak is happening where I add lots of records to a database. Checking the destructors is a good idea. Since I’m shifting around GBs of data there is now a global module holding the data. I’ll check that out, too.
For our console-based server, I created a runtime objects report that shows the number of each type of object and compares that to the last time the report was run. We caught a few leaks that way.
I’m mostly out today but will post the code as soon as I can if you think it would be helpful.
Actually, I had forgotten that my original code had been refactored into something cleaner using objects, but this is the meat of it.
// // Refresh the report with the latest objects in memory // dim report as new Dictionary ObjectCount = Runtime.ObjectCount MemoryUsed = Runtime.MemoryUsed // // Since we are getting a reference to every object, // any objects that rely on their destructors firing in // the Thread that created them may be thwarted. By // preventing background tasks, we should avoid that. // #pragma BackgroundTasks False dim iterator as Runtime.ObjectIterator = Runtime.IterateObjects while iterator.MoveNext dim o as object = iterator.Current dim ti as Introspection.TypeInfo = Introspection.GetType(o) dim key as variant = ti.FullName dim status as ObjectStatusItem = report.Lookup(key, nil) if status is nil then status = new ObjectStatusItem status.ClassName = key report.Value(key) = status end if status.InstanceCount = status.InstanceCount + 1 wend iterator = nil #pragma BackgroundTasks True if mLastReport isa Dictionary then // // Look for items in the old report and compare the instance counts // tracking the change // for each key as String in mLastReport.Keys dim thisStatus as ObjectStatusItem = report.Lookup(key, nil) dim oldStatus as ObjectStatusItem = mLastReport.Value(key) if thisStatus isa ObjectStatusItem then thisStatus.LastInstanceCount = oldStatus.InstanceCount end if next end if redim Items(-1) dim names() as String for each key as String in report.Keys names.Append key Items.Append report.Value(key) next names.SortWith(Items) mLastReport = report
One caveat: If you use any classes whose Destructor is responsible for calling CriticalSection.Leave, running this report will cause your app to come to throw an exception at best or come to a grinding halt at worst. Semaphores won’t have that issue.
@Kem Tekinay : thanks, I’ll try this out. One big culprit seems to be updating a dock image from a class. The MBS example uses a timer and this works fine. Doing the same with a class gives me a big memory leak. But that doesn’t seem to be all.