Skip to main content
Thoughts from David Cornelius

Category

Is your application eating up memory?

The Problem

I've been working with a small team on upgrading several dozen accounting programs that have been maintained by programmers of varying expertise over the last three decades. All these programs are written in Delphi 5 and use the BDE for database access, several third-party libraries, and quite a large collection of hand-crafted components. As we upgrade this large code base to Delphi 12, we've been rewriting several of the libraries, replacing components, and swapping out the BDE with FireDAC. As can be expected, there's quite a bit of technical debt to deal with including duplicate functions, mysterious code without documentation, ignored compiler warnings, business rules tied to button clicks and BeforePost events, and of course, lots of memory leaks. It's a huge, multi-year endeavor and I'm constantly searching for ways to automate the process and find problems quicker in order to complete this migration before I retire!

While it may not be imperative to eliminate every single memory leak, it's wise to discover and remove as many as feasible, especially in code that could be reused; you never know how a small problem can be magnified if left untreated. One example of this I found in my discovery was a base data module that always loaded several records into a global object for convenient use throughout the program. In most cases, this was fine (disregarding the principal of avoiding global variables), but since this data module was used by other data modules there were some programs that indirectly used this base data module twice. Since the data module did not check to see if the global variable had already been allocated, the program ended up creating it twice but only freeing it once (wisely setting it to nil so it wouldn't inadvertently free it again). What's worse is that, depending on the scope of the calling methods, it's possible that different instances of the global variable would be accessed at different times and if values changed, it could lead to inconsistencies which are very hard to track down.

Started with FastMM4

I started using FastMM4 to find out how bad the problem was and quickly became discouraged. When the program exits, FastMM4 displays a list of types and byte counts of objects that were not properly freed. In a few of the larger programs, this list of small text nearly filled my screen! I would then need to look at the object types and hunt through the code for all instances of that object type and find the cases where it was created but not freed. This is a very long and tedious process for large programs with an abundance of memory leaks.

I started reading forum discussions and blog posts about debugging memory leaks and while there were several references to FastMM4 and praises over what it can do, I also found several references to a tool called, Deleaker. I had heard of Deleaker but had not taken the time to look closely. With renewed interest and a strong need for all the help I could get, I read the Embarcadero blog and followed it to the video. The demo was simple but showed promise, so I downloaded and installed a trial to see if it would help me debug faster.

And wow, was I impressed!

Introducing Deleaker

Deleaker works as both a stand-alone tool and as a floating menu tightly integrated with Delphi. (It also works with C++Builder, Visual Studio, and Qt Creator providing information about both managed and unmanaged code.) The setup instructions are simple and include steps on how to compile your applications with all the debugging info included to enable Deleaker to provide you with detailed information about the objects and memory allocation in your program. I was soon taking "before" and "after" snapshots of my compiled application and looking at the report.

I first chose a small project that had a small number of memory leaks. Here's the FastMM4 report:

Leaks reported by FastMM4

One thing I noticed in the FastMM4 memory leak summaries were several instances of UnicodeString. Strings in Delphi are automatically freed when they go out of scope so should never raise a memory leak error, right? Well, if they are part of an object that didn't get freed, then they're listed as one of the memory leaks along with the un-freed object itself. That makes sense and that's not an incorrect report, but it can be misleading.

Instead of a simple list of leaked objects, Deleaker provides an interactive window showing objects that were not properly freed and the line in which they were created. As you click on each one, it lists the hit count, size, process ID, thread ID, timestamp, and more. All of these are in a grid where each column header can be clicked to sort the list by that column. Additionally, the call stack that led up to that memory allocation is listed in the bottom portion of the window.

Here's the same program's memory leaks as shown by Deleaker:

Memory leaks reported by Deleaker

This view is sorted by size, descending; the largest memory leaks are first. When I click on the first line, a TStringList, the source lines for each of the five instances are shown (highlighted) on the right. I clicked on the first source line and the call stack leading up to it is shown in the bottom.

If I click on the last reported memory leak, a TIniFile, it only has one instance, highlighted on the right with the call stack on the bottom:

Memory leaks reported by Deleaker (2)

For even more convenience, you can click the Show Source Code button which takes you right to that line in Delphi! Now, instead of hunting around for the source of a memory leak, I was taken directly to its source and could start evaluating the code immediately.

With a cleaner view of the problematic objects and a direct line to the source, I started cleaning up memory leaks much more quickly. But another advantage is that I had confidence that the line reported was the actual source of the leak. With FastMM4, all I had was a list of objects; it was up to me to figure out which of the 19 TStringList objects in this particular project led to the 5 reports of that object's leaks. In one case, I had seen an object created and freed and so had passed it by, thinking it was okay. However, Deleaker reported that this particular instance was not properly freed; upon closer inspection, I found it was in a base data module (the one I mentioned above) that used a global variable but since it was used by two descendant data modules (both used by the project), it was allocated twice; it's freeing code set the variable to nil so it was only freed once. Without Deleaker telling me I should look at that object more closely, it would've taken me much longer to find this one after eliminating the other possibilities.

In a different project, I had noticed several hundred UnicodeStrings reported by FastMM4 in a big list of memory leaks (rather overwhelming to the eye!). The list in Deleaker didn't show the strings but it's much more streamlined interface allowed me to see one particularly suspicious object. It stood out because it was reported as having hundreds of creation instances that matched the count of  UnicodeStrings reported in FastMM4. Looking at the code, I found it was loading string fields from a database into an array and once I added the missing Free statement, the UnicodeStrings were no longer reported by FastMM4.

This is another example of how Deleaker improves memory-leak debugging: just being able to see the object types in a nice column rather than a tight, word-wrapped list in a small message box.

Even More

Here are a few more things about Deleaker you may have wondered about after seeing the screenshots above:

  • Up near the top is a drop-down for Allocation Types which allows you to filter certain types of objects out of the list the next time you take a snapshot.
  • You can take snapshots at any point during your program's execution, save them, and then compare with other saved snapshots for better analysis of different scenarios.
  • The Allocations tab, available for all languages, shows an ungrouped list of memory leaks and the line where each is allocated; the Delphi Objects tab (only available for Delphi, obviously) shows the same number of leaks but groups them by Delphi Object type making it a nicer view.
  • The Modules tab shows all the DLLs and BPLs which your program referenced.

There are other features in this product, several of which I’m not yet using. One of those is a command-line tool to provide automation within your continuous-integration environment. Read the Deleaker docs for more information and links to videos, tutorials, blogs, and more.

Disclosure

I should mention I was given a license to Deleaker in exchange for writing this blog post. However, it did not influence my excitement over this product which has already saved me hours of analyzing a large code base. They say time is money and for me, the price is quickly justified. [I would like to personally thank Artem Razin for this amazing product and for reaching out to me!]

Summary

Deleaker can speed up memory leak detection by an order of magnitude!

I don’t want to down-play the use of FastMM4 as an option to debugging memory leaks in your Delphi programs. It’s free, has well-written code and inline documentation, and was the first step to letting me know I had some problems with the code. FastMM4 also has lots of configurable options to help track down memory leaks, hook into processes, and even log many more details to a file for deep analysis. To enable these options, you have to read through a few hundred lines of a Pascal INCLUDE file and enable various compiler directives. Since it's free and open source, you should definitely start there. 

Additionally, I’ve learned about its successor, FastMM5; it has been rewritten to be faster and while it’s still open source, it has a commercial license option. I first tried FastMM4, then got hooked on Deleaker so didn’t take the time to work with FastMM5.

In either case, the ease of use of setting up Deleaker and its ability to pinpoint the source of memory leaks and list the call stack rather than just a collection of leaked objects, has increased my productivity and reduced the reluctance to hunt down memory leaks. My experience with Deleaker has sped up memory leak detection by an order of magnitude!

david Mon, 02/03/2025 - 08:17

In reply to by DelphiCoder (not verified)

Yes, I've heard about these debugging aids for many years and even have a license to an old version of EurekaLog but they are geared more for locating bugs and reporting what caused an exception, especially when encountered at a customer location. On EurekaLog's website, it says its memory leak catching feature is light-weight to minimize the impact on end-user machines and even recommends other debugging tools. Deleaker is designed to be used by the developer and gives a lot of information that would not only confuse a customer but be totally useless to them.

I have not used madExcept but did not find anything on its help page about debugging memory leaks. 

If you have experience about either of these tools that show how they help with memory-leak detection, please post it here.

Add new comment

The content of this field is kept private and will not be shown publicly.