Не используется ли память для приложений Delphi, работающих в Windows Server 2008 (sp1)?

У нас есть приложение D2007, объем памяти которого неуклонно растет при работе в Windows Server 2008 (x64, sp1).
Оно нормально ведет себя в Windows Server 2003 (x32 или x64), XP и т. Д., Где оно то вверх, то вниз как и ожидалось.
Мы пробовали с включенным диспетчером памяти или последней версией FastMM4 4.92 с теми же результатами.

Кто-нибудь пытался контролировать использование памяти любым приложением Delphi на Win2008 и подтвердил бы это?
Или будет какая-нибудь подсказка?

Точность:
- отсутствие утечек памяти в обычном смысле (и да, я хорошо знаком с FastMM и др.)
- использование памяти отслеживалось с помощью Process Explorer; как виртуальная память (частные байты), так и физическая память (рабочая память, частная) в Win2008 увеличиваются. Потребление памяти все еще растет даже при нехватке памяти. (вот как мы пришли исследовать, поскольку это привело к отказу, но только на коробках Win2008)

Обновление: // ** измененный ** // код намного проще, чем наше приложение, но показывает то же поведение.
Создание списка из 10 000 000 объектов, затем 10 000 000 интерфейсов, выполненных в 2 раза, увеличивает использовала память на ~ 60 МБ и примерно на 300 МБ больше для 100 дополнительных запусков в Windows Server 2008, но просто возвращается туда, где она была в XP.
Если вы запускаете несколько экземпляров, память не освобождается, чтобы позволить другим экземплярам работать. Вместо этого файл подкачки растет, а сервер сканирует ...

Обновление 2: см. отчет о контроле качества 73347
После дальнейшего исследования мы отследили его до критических секций, как показано в приведенном ниже коде.
Поместите этот код в простое приложение VCL с помощью кнопки. И отслеживайте с помощью Process Explorer:
он начинается с ~ 2,6 МБ и после 5 запусков (нажатий на кнопку) остается на уровне ~ 118,6 МБ.
116 МБ потеряно за 5 запусков.

//***********************
const
  CS_NUMBER = 10000000;
type
  TCSArray = Array[1..CS_NUMBER] of TRTLCriticalSection;
  PCSArray = ^TCSArray;

procedure TestStatic;
var
  csArray: PCSArray;
  idx: Integer;
begin
  New(csArray);

  for idx := 1 to length(csArray^) do
    InitializeCriticalSection(csArray^[idx]);

  for idx := 1 to length(csArray^) do
      DeleteCriticalSection(csArray^[idx]);

  Dispose(csArray);
end;

procedure TestDynamic(const Number: Integer);
var
  csArray: array of TRTLCriticalSection;
  idx: Integer;
begin
  SetLength(csArray, Number);

  for idx := Low(csArray) to High(csArray) do
    InitializeCriticalSection(csArray[idx]);

  for idx := Low(csArray) to High(csArray) do
      DeleteCriticalSection(csArray[idx]);
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
  TestStatic;
  TestDynamic(CS_NUMBER);
end;

person Francesca    schedule 23.04.2009    source источник
comment
Вы имеете в виду частные байты, виртуальный размер или рабочий набор? Запустите Process Explorer из SysInternals, чтобы отслеживать память и лучше понимать, что происходит. technet.microsoft.com/en-us/sysinternals/bb896653.aspx   -  person Jim McKeeth    schedule 23.04.2009
comment
Можете ли вы воспроизвести проблему, если не вызываете TestMemoryInterfaces?   -  person Alex    schedule 25.04.2009
comment
Я спросил об этом, потому что вы передаете MyList.Add (TInterfaceList.Create ()). При выходе из конструктора интерфейс имеет нулевое количество ссылок. Так что может быть место и плохим вещам (извините, у меня нет под рукой Delphi, чтобы проверить это предположение). Я определенно видел отчет QC об этой проблеме: пользователь жалуется на возможную скрытую ошибку в аналогичном коде. Обходной путь - использовать явную переменную: I: = TInterfaceList.Create (); MyList.Add (I);   -  person Alex    schedule 25.04.2009
comment
Спасибо всем за разные идеи. См. Новое Обновление 2 и пример кода   -  person Francesca    schedule 30.04.2009
comment
Вы нашли способ решить эту проблему?   -  person Rodrigo Farias Rezino    schedule 05.08.2011
comment
вам необходимо заменить все вызовы к InitializeCriticalSection на вызовы к InitializeCriticalSectionEx и передать флаг CRITICAL_SECTION_NO_DEBUG_INFO.   -  person Francesca    schedule 06.08.2011


Ответы (8)


Появился новый инструмент sysinternals под названием VMMap, который визуализирует выделенную память. Может быть, он покажет вам, что такое большие блоки памяти.

person Steffen Binas    schedule 23.04.2009

Фактически, Microsoft внесла изменения в Критические разделы, чтобы добавить некоторую отладочную информацию. Эта отладочная память не освобождается до конца приложения, но каким-то образом кэшируется и повторно используется, поэтому через некоторое время она может выйти на плато.

Решение, если вы хотите создать много критических разделов, не ощущая этого недостатка памяти, состоит в том, чтобы исправить код VCL, чтобы заменить вызовы InitializeCriticalSection вызовами InitializeCriticalSectionEx и передать ему флаг CRITICAL_SECTION_NO_DEBUG_INFO, чтобы избежать создания структуры отладки.

person Francesca    schedule 05.08.2011

Вы включали FastMM с полным режимом отладки? Просто включите модуль FastMM4 прямо в свой проект и установите

ReportMemoryLeaksOnShutdown := True

Если ничего не сообщается, возможно, все нормально освобождается при выходе из программы (возможно, из-за подсчета ссылок). Вы можете использовать AQTime для мониторинга памяти в реальном времени. С помощью этого приложения вы можете видеть "подсчет" байтов для каждого имени класса и для остальной используемой памяти. Может быть, вы увидите, кто использует память. Для этой работы достаточно демо-версии с ограниченным сроком действия.

person Steffen Binas    schedule 23.04.2009
comment
Утечек нет, он ведет себя нормально, ЗА ИСКЛЮЧЕНИЕМ с Windows Server 2008. Так что он должен быть между MM и ОС. - person Francesca; 23.04.2009
comment
Когда приложение не протекает, AQTime не будет показывать возрастающие числа. Я бы попробовал, просто чтобы убедиться, что это не ваше приложение. - person Steffen Binas; 23.04.2009
comment
Как бы вы объяснили, что один и тот же код работает со всеми версиями Windows, ЗА ИСКЛЮЧЕНИЕМ Server 2008? Было бы забавно, если бы ОС могла добавлять утечки памяти в ваши EXE. - person Francesca; 24.04.2009
comment
В вызовах API могут быть незначительные изменения, которые могут искать ранее зарезервированные поля в структурах и интерпретировать их по-разному. При переходе на новую версию windows обнаружил несколько проблем (правда, утечки памяти не помню). - person Steffen Binas; 26.04.2009
comment
Это и есть! Они (Microsoft) изменили поведение критических секций, и это отстой! Спасибо! - person Francesca; 30.04.2009

Вы имеете в виду частные байты, виртуальный размер или рабочий набор? Запустите Process Explorer от SysInternals, чтобы контролировать память и лучше понимать что здесь происходит.

У меня нет особого опыта в этом (хотя я использую 2008 x64 SP1, поэтому могу его протестировать), но я собираюсь предложить вам создать тестовое приложение, которое выделяет кучу памяти, а затем освобождает ее. Запустите Process Explorer из SysInternals, чтобы контролировать память.

Если вы тестируете приложение, которое воспроизводит то же поведение, попробуйте создать некоторую нагрузку на память, выделив память в другом процессе - настолько, что это приведет к сбою, если ранее освобожденная память в первом процессе не будет восстановлена.

Если это по-прежнему не удается, попробуйте другой диспетчер памяти. Может, это делает FastMM.

person Jim McKeeth    schedule 23.04.2009
comment
Сделал тестовое приложение. Давили на память. Придется попробовать с другим менеджером памяти ... (отстой!) - person Francesca; 24.04.2009
comment
Значит, у тестового приложения было такое же поведение? - person Jim McKeeth; 25.04.2009
comment
да. Связано с изменением реализации критических секций Microsoft. См. Обновление 2. Спасибо! - person Francesca; 30.04.2009

Проверьте, есть ли у вас эта проблема (это другая проблема, не связанная с тот, о котором я упоминал в комментариях к вашему вопросу).

person Alex    schedule 25.04.2009

Я сделал этот код, чтобы исправить эту проблему в своих приложениях. То же самое и с FastCode, чтобы запустить исправление, вы должны поместить модуль в качестве первого модуля вашего проекта. Как и в случае с uRedirecionamentos: введите описание изображения здесь

unit uCriticalSectionFix;
// By Rodrigo F. Rezino - [email protected]

interface

uses
  Windows;

implementation

uses
  SyncObjs, SysUtils;

type
  InitializeCriticalSectionExProc = function(var lpCriticalSection: TRTLCriticalSection; dwSpinCount: DWORD; Flags: DWORD): BOOL; stdcall;

var
  IsNewerThenXP: Boolean;
  InitializeCriticalSectionEx: InitializeCriticalSectionExProc;

type
  PJump = ^TJump;
  TJump = packed record
    OpCode: Byte;
    Distance: Pointer;
  end;

  TCriticalSectionHack = class(TSynchroObject)
  protected
    FSection: TRTLCriticalSection;
  public
    constructor Create;
  end;

function GetMethodAddress(AStub: Pointer): Pointer;
const
  CALL_OPCODE = $E8;
begin
  if PBYTE(AStub)^ = CALL_OPCODE then
  begin
    Inc(Integer(AStub));
    Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
  end
  else
    Result := nil;
end;

procedure AddressPatch(const ASource, ADestination: Pointer);
const
  JMP_OPCODE = $E9;
  SIZE = SizeOf(TJump);
var
  NewJump: PJump;
  OldProtect: Cardinal;
begin
  if VirtualProtect(ASource, SIZE, PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    NewJump := PJump(ASource);
    NewJump.OpCode := JMP_OPCODE;
    NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);

    FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
    VirtualProtect(ASource, SIZE, OldProtect, @OldProtect);
  end;
end;

procedure OldCriticalSectionMethod;
asm
  call TCriticalSection.Create;
end;

{ TCriticalSectionHack }

const
  CRITICAL_SECTION_NO_DEBUG_INFO = $01000000;
  NEW_THEN_XP = 6;

constructor TCriticalSectionHack.Create;
begin
  inherited Create;
  if IsNewerThenXP then
    InitializeCriticalSectionEx(FSection, 0, CRITICAL_SECTION_NO_DEBUG_INFO)
  else
    InitializeCriticalSection(FSection);
end;

procedure AdjustMethod;
var
  LKernel32: HModule;
begin
  if IsNewerThenXP then
  begin
    LKernel32 := LoadLibrary('kernel32.dll');
    @InitializeCriticalSectionEx := GetProcAddress(LKernel32, 'InitializeCriticalSectionEx');
  end;
end;

initialization
  AddressPatch(GetMethodAddress(@OldCriticalSectionMethod), @TCriticalSectionHack.Create);
  IsNewerThenXP := CheckWin32Version(NEW_THEN_XP, 0);
  AdjustMethod;


end.
person Rodrigo Farias Rezino    schedule 04.08.2011

Помимо Александра, обычно это называется «фрагментация кучи».

Обратите внимание, что FastMM в целом должен быть более устойчивым и быстрым, но если исходное приложение было настроено для memmanager D7, FastMM на самом деле может работать хуже.

person Community    schedule 25.04.2009

Что ж, использование памяти может увеличиваться, даже если в вашем приложении нет утечки памяти. В таких случаях существует вероятность утечки другого ресурса. Например, если ваш код выделяет, скажем, растровое изображение и хотя он освобождает все объекты, но умудряется забыть о финализации некоторого HBITMAP.

FastMM сообщит вам, что в вашем приложении нет утечки памяти, поскольку вы освободили все свои объекты и данные. Но у вас по-прежнему происходит утечка других типов ресурсов (в моем примере - объектов GDI). Утечка других типов ресурсов также может повлиять на вашу память.

Предлагаю вам попробовать другой инструмент, который проверяет не только утечки памяти, но и другие типы утечек. Я думаю, что AQTime на это способен, но я не уверен.

Другая возможная причина такого поведения - фрагментация памяти. Предположим, вы выделили 2000 объектов размером 1 Мбайт (давайте на минутку забудем о MM накладных расходах и наличии других объектов в пользовательском пространстве). Теперь у вас есть полные 2 ГБ занятой памяти. Теперь предположим, что вы освободили все четные объекты, так что теперь у вас есть «очищенное» пространство памяти, в котором смешаны занятые и свободные блоки размером 1 Мбайт. Хотя теперь у вас есть 1 Гб свободной памяти, но вы не можете выделить память для любого 2-мегабайтного объекта, поскольку максимальный размер свободного блока составляет всего 1 Мбайт (но у вас есть 1000 таких блоков;)). Если диспетчер памяти использовал для ваших объектов блоки размером более 1 Мбайт, то он не может освободить блоки памяти обратно в ОС, когда вы освободили свои четные объекты:

[ [busy] [free] [busy] [free] [busy] [free] ]
[ [busy] [free] [busy] [free] [busy] [free] ]...

Эти большие блоки [...] наполовину заняты, поэтому MM не может передать их ОС. Если вы запросите другой блок размером> 1 Мб, MM потребуется выделить еще один блок из ОС:

[ [busy] [free] [busy] [free] [busy] [free] ]
[ [busy] [free] [busy] [free] [busy] [free] ]...
[ [your-new-object] [free.................] ]

Обратите внимание, что это всего лишь примеры увеличения использования памяти, хотя у вас нет утечки памяти. Я не говорю, что у вас ТОЧНАЯ ситуация: D

person Alex    schedule 24.04.2009
comment
Как это решить, что один и тот же код работает со всеми версиями Windows, ЗА ИСКЛЮЧЕНИЕМ Server 2008? - person Francesca; 24.04.2009
comment
Почему нет? Если в коде используются какие-то детали реализации, которые были изменены в 2008 / Vista, то почему бы и нет? Я имею в виду ваш код и ММ Delphi. - person Alex; 25.04.2009
comment
Это проблема не столько фрагментации памяти, сколько фрагментации адресного пространства. - person oɔɯǝɹ; 09.10.2009
comment
Вероятно, это зависит от того, как вы определяете память. Я не сказал, что это физическая память, верно? - person Alex; 09.10.2009