Solved

Memory leak detection and use of third party utilities

Posted on 2009-04-01
16
730 Views
Last Modified: 2012-05-06
I have a commercial app written in D5 which is leaking memory over time depending on how busy the application is processing.  I have used both Memchk and Eurekalog to try and pin down the cause, but am a bit confused with regard to the results...

In order to familiarise myself with Eureka log, I wrote a simple test app which would create a stringlist on a button click, without freeing the object afterwards.  If I click the button 5 times, then Eurekalog reports "Memory Leak: type TStringlist; total size=240 Count=5 -- i.e. it reports the number of times the suspect object is created and not freed.  In my commercial app however, I can have it process (records from a Zeos dataset) either 5 records or 5000 records and the "Count" values in the Eurekalog remain the same (i.e. there is no instance of 5 or 5000 in any of the Count fields) and total memory leak value remains about the same at approx 1Mb -- therefore I have to presume that after the initial reported memory leaks (system.pas, Classes.pas, ZList.pas, ZQuery.pas)  there is no further loss on that object.  

Therefore, how can I trace why the app is losing approx 25 Mb every 10 hours?
0
Comment
Question by:brenlex
  • 7
  • 6
  • 2
  • +1
16 Comments
 
LVL 36

Expert Comment

by:Geert Gruwez
ID: 24039766
system.pas and classes.pas ???

it looks like they create master component and keep using that same component to hang the rest on.
Now and then a refresh of the master component ?

the eurekalog keeps logging to a file, just let it do this and set it not stop the application
then check the logfile and see what eurekalog reports about the memoryleak
0
 

Author Comment

by:brenlex
ID: 24039917
I am letting the commercial app run for either a few seconds (to process 5 recs) or continuosly (to log the 5000 recs for example) -- regardless of how long I let the app run (and then I close app gracefully), the total memory leak reported and counts is roughly the same.  

Sorry Geert, I do not follow ... "refresh the master component" ?

From system.pas, reports on  _NewAnsiString (on numerous occasions) / _LStrSetLength;
From classes.pas, reports on  SetCapacity (in classes TList and TStringlist);
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 24041744
 It would help to see the Eurekalog report on the memory leak.  It is possible that you need to compile in the debug info for all of the Delphi and custom units you are using so that EurekaLog can trace the memory leak thoroughly.

  In the past, before I used EurekaLog, I put in my own custom memory allocation and deallocation routines in Delphi.  This would make very memory allocation or deallocation go through my code.  All the routines would do (for the memory operation) was call the original routines.  Every time memory was allocated I added the resulting pointer to a list.  Every time it was deallocated I pulled it off the list.  Obviously this list had to be statically allocated (if it grows or shrinks during usage it will throw the whole thing off).

  When the program ended I had a list of the memory leaks by memory address.  When I re-ran the program I was able to set a breakpoint in my memory allocation routine to stop when it returned the first memory address on my list.  This was not full proof.  I found out that the same address can be reused after it is initially allocated.  This means you may need to show how many times the address was allocated before you know it is stopped on the right allocation (the last one).  Once you are stopped at the right allocation you have a call stack to show what called it.

  You may be looking at memory leaks in Delphi's base code, or in the custom components you are using.  I have found, for instance, that if you dynamically create a dataset and pass nil as the owner you will get a memory leak.  Best in this instance to use a data module as the owner even if you will free it yourself.  The point of this is that the memory leak may not be in your code.


0
 

Author Comment

by:brenlex
ID: 24047813
As suggested, I have got full debug enabled -- I have the class/unti/method/line details reported -- though I am confused as to why I have such high memory loss when the 'count' values in the Eurekalog stay the same regardless of the number of records I process or the length of time the program is operating.  The other aspect which I cannot grasp is if the program processes a relatively low amount of data throughput (reading recs from MySQL via Zeos and passing across a TCP interface) the program can run for months without any visible memory loss, however, if it processes for example one record per second, the memory loss grows exponentially within a few hours !?!

Is it possible that Windows is misreporting the memory usage?  I am unfamiliar with the nuts and bolts of  how Delphi's memory management / garbage collection equiv is performed, but I am reading my memory stats from a MMC console and also reporting them programmatically (same vals).  Is there memory that has actually been freed but not picked up (or had time to be picked up) by Windows?
0
 
LVL 21

Accepted Solution

by:
developmentguru earned 250 total points
ID: 24051666
 A Delphi application handles it's own memory allocation and deallocation WITHIN the allocation of memory from Windows.  EurekaLog handles checking for memory leaks at this level.  Just because Windows shows a program as using more memory during it's run does NOT indicate a memory leak.  It is possible that the memory manager in Delphi built up it's memory usage to a point where it asked windows to change the size of the memory block it had previously allocated.  To Windows, that memory block will show the same value until the program shuts down.  You could dig into the memory allocation routines in Delphi and see if you can 1) determine if this is correct and 2) find a place to reallocate the Delphi memory heap smaller when it is under 50% utilized (for example).

  Once again, seeing the memory leak report you get from EurekaLog could help us determine what is going on.

  If you want to SEE what is happening you will need source for all components you are using and you will need to try the hook into the memory management I mentioned earlier.  Here is a starter unit.  You use this unit by selecting what section of code you want to capture information in.  You call HookMemoryManager before the code and UnHookMemoryManager to stop it.  I placed a blank stub in the UnHookMemoryManager routine where you can put in code to write out (to disk?) the pointer to each of the allocations that were not released.  I started with a list of up to 32,000 pointers it could dynamically manage.  If you need more, increase that number.  Once you stop your program in theUnHookMemoryManager procedure you will be able to see a count of the number of leaks.  You can then individually stop the program in the allocation of the pointer involved until you see the last allocation of that pointer, rerun the application making sure it follows the same steps, and stop it on the last allocation of that pointer (breakpoint properties set to 'Result = pointer($AEDEB0)' in HookGetMem).  At that point you have the Delphi call stack you can look through to see where it happened.

  I wrote the unit today and it is tested on Delphi 2007.  Keep in mind that Delphi responds to windows messages that activate code outside of what you test so you will see some leakage that 1) may not be a leak and 2) is not from the area of code you are watching.  Keeping that in mind it can still be a huge help to be able to stop the program at the instant it causes the leak.
unit uMemoryManagerHook;
 

interface

uses

  Classes, SysUtils;
 

procedure HookMemoryManager;

procedure UnHookMemoryManager;
 

implementation

var

  OldMemoryManager, NewMemoryManager : TMemoryManagerEX;

  PtrList : TList;
 

function HookGetMem(Size : Integer) : Pointer;

begin

  Result := OldMemoryManager.GetMem(Size);
 

  //This is where you set the breakpoint to stop on a specific memory allocation

  //Remember: you may need to count how many times a run of a program will stop

  //here in order to find the LAST time it was created.  Then re-run the program

  //and stop it on the last time to trace the stack back to the offending

  //instruction.

  if PtrList.Count >= 32000 then //we do NOT want this to grow

    raise Exception.Create('Hooked memory manager overflow.  ' +

      'Increase initial size.');
 

  if PtrList.IndexOf(Result) = -1 then

    PtrList.Add(Result);

end;
 

function HookFreeMem(P : Pointer) : integer;

var

  I : integer;
 

begin

  I := PtrList.IndexOf(P);

  if I <> -1 then

    PtrList.Delete(I);

  Result := OldMemoryManager.FreeMem(P);

end;
 

procedure HookMemoryManager;

begin

  GetMemoryManager(OldMemoryManager);

  Move(OldMemoryManager, NewMemoryManager, SizeOf(TMemoryManagerEX));

  NewMemoryManager.GetMem := HookGetMem;

  NewMemoryManager.FreeMem := HookFreeMem;

  SetMemoryManager(NewMemoryManager);

end;
 

procedure UnHookMemoryManager;

begin

  SetMemoryManager(OldMemoryManager);

  if PtrList.Count > 0 then

    begin

      //write out the leeks

    end;

end;
 

procedure Initialize;

begin

  PtrList := TList.Create;

  PtrList.Capacity := 32000;

end;
 

procedure Finalize;

begin

  PtrList.Free;

end;
 

initialization

  Initialize;
 

finalization

  Finalize;
 

end.

Open in new window

0
 
LVL 19

Expert Comment

by:MerijnB
ID: 24064378
If you're using D5, it is very well possible that you don't really leak memory, but suffer from memory fragmentation. If this is the case, you won't find a 'real' leak.

I'd suggest you run with FastMM and see how much you leak then (don't use any leak detection, just let it run and see if you still loose 25 MB in 10 hrs).
This leads to another question: what do you use to detect you loose the memory?
0
 

Author Comment

by:brenlex
ID: 24073643
Tried with both Memchk and EurekaLog initially. I have implemented fastmm but have now run into a further issue -- without any debugging enabled (as I initially just wanted to check on the app's performance with a replacement mem mgr before attempting further mem diags), my app hangs within a minute of starting. Other than specifying fastmm at the top of my project's uses section is there anything else I should be aware of for setup? Note: I have used a handy FastMM options interface to edit the content of the fastmmoptions.inc

Also, I have noticed that my app runs an additional thread when fastmm compiled in - is this normal?
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 24074524
Both question (1 minute hang time and extra thread) depend on the content of FastMMOptions.inc, can you show what you have in there now?
0
Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

 

Author Comment

by:brenlex
ID: 24075265
Sure thing... file attached (extn renamed to txt).
FastMM4Options.txt
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 24075635
The options look just fine. The extra thread is quite possibly created by FastMM, I wouldn't worry about it.

On the hang after 1 minute: Do you still have Eurekalog leak detection (or other leak detection mechanism) running?
0
 

Author Comment

by:brenlex
ID: 24075907
No leak detection mechanisms in place -- I just wanted to try out FastMM as my memory manager initially.

Going back a step... if my app never used ShareMem in the first place, is it pointless for me to be trying FastMM anyway?
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 24075918
No!

You should _definitely_ switch to FastMM. It is so much better than the default MM in all Delphi versions before 2006.

You still have the hang after 1 minute? Does it hang every single time? Can you pinpoint where it hangs?
0
 

Author Comment

by:brenlex
ID: 24076201
I am trying to debug hangs at the moment -- will come back to you shortly.

With regard to mem management - I am unfamiliar with D5's method of applying memory management under the hood to my compiled apps.  I replaced the BorlndMM.dll found in my C:\Program Files\Borland\Delphi5\Bin directory in accordance with the readme that came with FastMM ... in order to "speed up the IDE" -- I am presuming this is its sole purpose.  If I don't use the FastMM4 ref in my uses clause then I presume D5 uses a different (default delphi mem management) ref when compiling the app? Is this correct?
0
 
LVL 19

Assisted Solution

by:MerijnB
MerijnB earned 250 total points
ID: 24076437
> ... in order to "speed up the IDE"
I'd suggest to not use FastMM for this, but DelphiSpeedUp: http://andy.jgknet.de/blog/?page_id=8

> If I don't use the FastMM4 ref in my uses clause then I presume D5 uses a different (default delphi mem management) ref when compiling the app? Is this correct?

Yes. The default MM is much slower, but besides that, is very prone to memory fragmentation. This can very well cause your original problem (loosing memory over time), especially if your application is 'busy' all the time and mixing small memory needs with large memory needs.




0
 

Author Comment

by:brenlex
ID: 24085643
My hangs seem to be related to the speed at which the app is processing a backlog of events (thread reading recs from MySQL table then signalling processing thread for further action). If I slow down the throughput (eg. create a new record every couple of seconds in real time) it seems to remain stable... though still suffering memory loss over a lengthy period of time.  I have implemented use of SetProcessWorkingSetSize(GetCurrentProcess, $FFFFFFFF, $FFFFFFFF) when the apps mem usage hits a specified ceiling, however my virtual memory slowly creeps (though much less with FastMM in use).

Can you suggest the recommended setup of FastMM for optimum performance (with NO debug required) on a multi threaded app? Are there any FastMM calls that can / need to be called from code to further optimise?
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 24225195
No need to call anything in FastMM.
There is an option in fastmm4options.inc which says something about multiple threads.
Besides that, select the 'release' option in fastmm4options.inc
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now