53

I know how to get the System memory use using GlobalMemoryStatusEx, but that tells me the what the entire OS is using.

I really want my program to report how much memory it alone has allocated and is using.

Is there any way within my Delphi 2009 program to call either a Windows function or maybe some FastMM function to find out the memory that has been allocated by my program alone?


Revisiting my question, I have now changed my accepted answer to the GetMemoryManagerState answer by @apenwarr. It produced identical results to the GetHeapStatus function (now deprecated) that I used to use, whereas GetProcessMemoryInfo.WorkingSetSize gave a very different result.

lkessler
  • 19,819
  • 36
  • 132
  • 203

6 Answers6

84

You can get useful memory usage information out of the Delphi runtime without using any direct Win32 calls:

unit X;

uses  FastMM4; //include this or method will return 0.
....

function GetMemoryUsed: UInt64;
var
  st: TMemoryManagerState;
  sb: TSmallBlockTypeState;
begin
  GetMemoryManagerState(st);
  result :=  st.TotalAllocatedMediumBlockSize
           + st.TotalAllocatedLargeBlockSize;
  for sb in st.SmallBlockTypeStates do begin
    result := result + sb.UseableBlockSize * sb.AllocatedBlockCount;
  end;
end;

The best thing about this method is that it's strictly tracked: when you allocate memory, it goes up, and when you deallocate memory, it goes down by the same amount right away. I use this before and after running each of my unit tests, so I can tell which test is leaking memory (for example).

lkessler
  • 19,819
  • 36
  • 132
  • 203
apenwarr
  • 10,838
  • 6
  • 47
  • 58
  • 7
    This method is valid when using FastMM4 or delphi 2006+, but if you are not using FastMM4, you should consider start using it! – Khalid Salomão Jul 29 '09 at 20:50
  • 7
    This may be the best and most useful answer I've ever seen on StackOverflow. I wish I could upvote it 100 times. – Nick Hodges Jun 03 '13 at 14:55
  • 1
    besides the above, this method shows the amount of memory *allocated* by the application, not the amount of memory used by it (like memory allocated by 3rd party dlls, ocx/COM, etc) For that, a much more reliable solution is the one given by Jim McKeeth below, provided that MemCounters.PagefileUsage is also added to the result. – ciuly Oct 24 '13 at 14:48
  • 5
    Small remark: You have to **use** (=set it in a uses section) FastMM4 in the unit that you are implementing this. It's not enough to just add FastMM4 in the project unit. – Roeland Van Heddegem Nov 06 '13 at 13:24
  • This doesn't seem to work with XE7, the psAPI method below this answer does though. – hikari Sep 21 '14 at 02:07
  • 1
    @rvheddeg Incredibly important "small remark". It will just return 0 if you don't do this. It saved my day, thank you. – drakorg Dec 09 '16 at 03:00
  • @Eduardo Thanks for the nice remark. But upvoting useful posts is even better! ;-) – Roeland Van Heddegem Dec 12 '16 at 10:50
  • @rvheddeg Can you please explain a little more detail what I need to add in uses? I am not sure what _(=set it in a uses section)_ means. – Makla Jan 23 '18 at 08:27
  • 1
    @Makla Just add "FastMM4" to the uses section in the unit that is implementing this functionality. I can't explain it better then this... – Roeland Van Heddegem Jan 23 '18 at 09:53
  • 1
    I've been using this in my program since you answered my question 9 years ago, but now that I'm switching to 64-bit, the number came out too small for what I thought it should be. Sure enough, I think I've found what should be a small modification, which I've now made to your answer. – lkessler Feb 13 '18 at 03:30
  • 1
    @lkessler I don't think you're correct about multiplying the totals by the block counts. There are two clues for that in `FastMM4.pas`. Look at how `GetMemoryManagerState()` loops over the blocks to build up the totals, and then at `GetMemoryManagerUsageSummary()` using those as is. – Thijs van Dien Jul 30 '18 at 17:56
  • I used this for my 64-bit application loading bitmaps. It yielded totally wrong results ( 7 MB instead of 4 GB). Then I used `GetMemoryManagerUsageSummary()` directly with a result of 70 MB. Jim McKeeth' solution rendered a sensible value. – stackmik Jan 27 '23 at 20:30
28

From an old blog post of mine.

Want to know how much memory your program is using? This Delphi function will do the trick.

uses psAPI;

{...}

function CurrentProcessMemory: Cardinal;
var
  MemCounters: TProcessMemoryCounters;
begin
  MemCounters.cb := SizeOf(MemCounters);
  if GetProcessMemoryInfo(GetCurrentProcess,
      @MemCounters,
      SizeOf(MemCounters)) then
    Result := MemCounters.WorkingSetSize
  else
    RaiseLastOSError;
end;

Not sure where I got the basics of this, but I added some better error handling to it and made it a function. WorkingSetSize is the amount of memory currently used. You can use similar code to get other values for the current process (or any process). You will need to include psAPI in your uses statement.

The PROCESS_MEMORY_COUNTERS record includes:

  • PageFaultCount
  • PeakWorkingSetSize
  • WorkingSetSize
  • QuotaPeakPagedPoolUsage
  • QuotaPagedPoolUsage
  • QuotaPeakNonPagedPoolUsage
  • QuotaNonPagedPoolUsage
  • PagefileUsage
  • PeakPagefileUsage

You can find all of these values in Task Manager or Process Explorer.

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
Jim McKeeth
  • 38,225
  • 23
  • 120
  • 194
  • The output number of this function is constantly growing and shows already 7000064 bytes RAM used, but task manager shows that the process is using 1972 kb of RAM. By the way, the link to your blog is dead. – Paul Sep 22 '21 at 10:34
  • Shouldn't the function result be NativeUInt instead of Cardinal so you get a 64-bit result when compiled to a 64-bit app? – kbriggs Apr 24 '22 at 21:38
  • That was probably 32 bit code, but for 64 bit you would want NativeUInt – Jim McKeeth May 25 '22 at 19:57
4

You can look at an example on how to use FastMM with the UsageTrackerDemo project that comes included with the Demos when you download the complete FastMM4 bundle from SourceForge.

Francesca
  • 21,452
  • 4
  • 49
  • 90
4

I wrote this small function to return the current process (app) memory usage:

function ProcessMemory: longint;
var
  pmc: PPROCESS_MEMORY_COUNTERS;
  cb: Integer;
begin
  // Get the used memory for the current process
  cb := SizeOf(TProcessMemoryCounters);
  GetMem(pmc, cb);
  pmc^.cb := cb;
  if GetProcessMemoryInfo(GetCurrentProcess(), pmc, cb) then
     Result:= Longint(pmc^.WorkingSetSize);

  FreeMem(pmc);
end;
  • 2
    Why use GetMem? Just declare a TProcessmemoryCounters variable right there instead of using a dynamic one. – Rob Kennedy Jan 13 '09 at 18:53
  • This is the answer, with Rob's optimization. – lkessler Jan 16 '09 at 03:59
  • 2
    It depends on what you think memory usage is. This code gives you the woking set size and is what task manager calls memory usage. But it is by far not the amount of memory a process is using. It is the part that currently is in RAM instead of the page file. – Lars Truijens May 21 '11 at 15:26
2

Conversion of Gant C ++ code, to console application in Delphi:

    program MemoryProcessCMD;

    {* Based in Gant(https://stackoverflow.com/users/12460/gant) code*}


    {$APPTYPE CONSOLE}
    {$R *.res}

    uses
      System.SysUtils,
      psapi,
      Windows;

    procedure PrintMemoryInfo(processID: DWORD);
    var
      hProcess: THandle;
      pmc: PROCESS_MEMORY_COUNTERS;
      total: DWORD;

    begin

      // Print the process identifier.
      Writeln(format('Process ID: %d', [processID]));

      // Print information about the memory usage of the process.
      hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, FALSE,
        processID);

      if (hProcess = 0) then
      begin
        exit;
      end;

      if (GetProcessMemoryInfo(hProcess, @pmc, SizeOf(pmc))) then
      begin
        Writeln(format(#09'PageFaultCount: 0x%.8X', [pmc.PageFaultCount]));
        Writeln(format(#09'PeakWorkingSetSize: 0x%.8X', [pmc.PeakWorkingSetSize]));
        Writeln(format(#09'WorkingSetSize: 0x%.8X', [pmc.WorkingSetSize]));
        Writeln(format(#09'QuotaPeakPagedPoolUsage: 0x%.8X',
          [pmc.QuotaPeakPagedPoolUsage]));
        Writeln(format(#09'QuotaPagedPoolUsage: 0x%.8X',
          [pmc.QuotaPagedPoolUsage]));
        Writeln(format(#09'QuotaPeakNonPagedPoolUsage: 0x%.8X',
          [pmc.QuotaPeakNonPagedPoolUsage]));
        Writeln(format(#09'QuotaNonPagedPoolUsage: 0x%.8X',
          [pmc.QuotaNonPagedPoolUsage]));
        Writeln(format(#09'PagefileUsage: 0x%.8X', [pmc.PagefileUsage]));
        Writeln(format(#09'PeakPagefileUsage: 0x%.8X', [pmc.PeakPagefileUsage]));
        Writeln(format(#09'PagefileUsage: 0x%.8X', [pmc.PagefileUsage]));
      end;

      CloseHandle(hProcess);
    end;

    var
      aProcesses: array [0 .. 1024] of DWORD;
      cbNeeded, cProcesses: DWORD;
      i: Integer;

    begin
      try
        // Get the list of process identifiers.
        if (not EnumProcesses(@aProcesses, SizeOf(aProcesses), &cbNeeded)) then
          halt(1);

        // Calculate how many process identifiers were returned.
        cProcesses := cbNeeded div SizeOf(DWORD);

        // Print the memory usage for each process
        for i := 0 to cProcesses - 1 do
        begin
          PrintMemoryInfo(aProcesses[i]);
        end;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;

    end.
1

For Win32 API way, you need GetProcessMemoryInfo function. Here is an example from MSDN page but the code is in C++. I think you can convert it to Delphi as well. What you are looking is probably called "Working Set Size."

#include <windows.h>
#include <stdio.h>
#include <psapi.h>

void PrintMemoryInfo( DWORD processID )
{
    HANDLE hProcess;
    PROCESS_MEMORY_COUNTERS pmc;

    // Print the process identifier.

    printf( "\nProcess ID: %u\n", processID );

    // Print information about the memory usage of the process.

    hProcess = OpenProcess(  PROCESS_QUERY_INFORMATION |
                                    PROCESS_VM_READ,
                                    FALSE, processID );
    if (NULL == hProcess)
        return;

    if ( GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc)) )
    {
        printf( "\tPageFaultCount: 0x%08X\n", pmc.PageFaultCount );
        printf( "\tPeakWorkingSetSize: 0x%08X\n", 
                  pmc.PeakWorkingSetSize );
        printf( "\tWorkingSetSize: 0x%08X\n", pmc.WorkingSetSize );
        printf( "\tQuotaPeakPagedPoolUsage: 0x%08X\n", 
                  pmc.QuotaPeakPagedPoolUsage );
        printf( "\tQuotaPagedPoolUsage: 0x%08X\n", 
                  pmc.QuotaPagedPoolUsage );
        printf( "\tQuotaPeakNonPagedPoolUsage: 0x%08X\n", 
                  pmc.QuotaPeakNonPagedPoolUsage );
        printf( "\tQuotaNonPagedPoolUsage: 0x%08X\n", 
                  pmc.QuotaNonPagedPoolUsage );
        printf( "\tPagefileUsage: 0x%08X\n", pmc.PagefileUsage ); 
        printf( "\tPeakPagefileUsage: 0x%08X\n", 
                  pmc.PeakPagefileUsage );
    }

    CloseHandle( hProcess );
}

int main( )
{
    // Get the list of process identifiers.

    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;

    if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )
        return 1;

    // Calculate how many process identifiers were returned.

    cProcesses = cbNeeded / sizeof(DWORD);

    // Print the memory usage for each process

    for ( i = 0; i < cProcesses; i++ )
        PrintMemoryInfo( aProcesses[i] );

    return 0;
}
Gant
  • 29,661
  • 6
  • 46
  • 65