Memory use in Delphi

Introduction

A while ago I discovered that one of my programs at work was having memory leaks, not a lot every time, but enough to become a problem in the long term.

When you discover a memory related problem, the usual approach is to manually inspect the section of code which you think is causing the memory leak, that is finding the place where you're allocating memory which is not being freed, or else try out one of those monitoring programs to help find the problem (I haven't have much like with them).

As the program has too many lines to make a random inspection without knowing exactly where is the memory being lost I though the easiest way to delimit the search would be to monitor the memory use of the program (which sounds easy but it isn't).

Memory use

Getting the real memory use of a process is something far for simple, in the first place because there are no specific functions which retrieve the exact quantity of memory used up by the process, and secondly because it's difficult to count up which memory belongs to the process and which one doesn't (for example, a dll is a dynamically loaded library which load only once in memory so that the memory consumed by the dll may or may not be inside what we consider the memory workspace of the process).

GetProcessMemoryInfo

In order to retrieve the memory used up by the program we will use two functions from the Windows API, the first one is [OpenProcess] which will allow us to get a handle (a handle is an integer used by windows to uniquely identify what is what and who is who) of the process that we want to get the memory information from and the second one is the [GetProcessMemoryInfo] which returns a structure with the information about the process memory ussage.

function GetMemoryUsage(pid : cardinal) : DWORD;
var
  hdl : cardinal;
  pcb : PROCESS_MEMORY_COUNTERS;
begin
  result := 0;
  // Open the process
  hdl := OpenProcess(PROCESS_QUERY_INFORMATION,false,pid);

  if hdl >; 0 then
  begin
    // Get the memory information
    GetProcessMemoryInfo(hdl,@pcb,sizeof(pcb));
    result := pcb.WorkingSetSize;
  end;
end;

  • First we open the process using the PROCESS_QUERY_INFORMATION security directive, which states that we want to query information about the process.
  • Once we have opened the process we call the GetProcessMemoryInfo function providing the handle we've just obtained and a reference to a PROCESS_MEMORY_COUNTERS struct that will be filled by the function, the last parameter is the total size of the struct.
  • Last we return the total memory used as the working set size of the process.

PROCESS_MEMORY_COUNTERS

The struct that gets filled by the GetProcessMemoryInformation has several fields. In the previous step we have used two of them in order to get the working set of the process but there's in fact a lot of information there.

typedef struct _PROCESS_MEMORY_COUNTERS {
  DWORD cb;
  DWORD PageFaultCount;
  SIZE_T PeakWorkingSetSize;
  SIZE_T WorkingSetSize;
  SIZE_T QuotaPeakPagedPoolUsage;
  SIZE_T QuotaPagedPoolUsage;
  SIZE_T QuotaPeakNonPagedPoolUsage;
  SIZE_T QuotaNonPagedPoolUsage;
  SIZE_T PagefileUsage;
  SIZE_T PeakPagefileUsage;
} PROCESS_MEMORY_COUNTERS

  • cb: The struct size.
  • PageFaultCount: Indicates the number of page faults for the process. A page fault is an access to a memory section of a process which is no longer located in RAM memory and that has triggered a context switch, stopping the process so that the operating system brings the page from wherever it has to (hard drive or swap) to main memory and then restart the process execution.
  • PeakWorkingSetSize: Indicates the maximum value in bytes the working set size has ever had while the process was running. All values preceded by Peak reflect the maximum value of what they're refered to.
  • WorkingSetSize: Indcates the current size of the working set in bytes. The woriking set size of a process is the actual size of the process which is actually in main memory.
  • PageFileUsage: Indicates the current size in bytes of the virtual memory used up by the process. Some of this pages can be in main memory too.
  • PagedPool and NonPagedPool: This two values represent the size used up by the process in pool paged memory and non paged memory which basically represent the size of pages in memory divided into two categories. PagedPool is memory which is allowed to be paged, that is, sent to disk if needed, NonPaged Pool represent memory that can't be sent to disk.

The problem with this method is that we are using WorkingSetSize as if it was the total memory used up by the process which is false as that date refers only to the "size of the memory which is currently in main memory (RAM)", but anyway, in order to measure memory losses or to get a general picture of how many memory a process is using and how it can very well work.

Final Note
We could easily integrate this function with the TProcessInformation that I showed you in another article about how to Get+process+and+thread+cpu+time+and+load.
7.5
Average: 7.5 (2 votes)
Your rating: None