Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Утечка ВИРТУАЛЬНОЙ памяти?


Автор: zedx 18.5.2009, 20:35
Собственно, ситуация: в проекте очень активно используются переменные TMemoryStream, причём само приложение многопоточное. (приложение - прокси-сервер, многопоточность - за счёт Indy (TIdHTTPServer)). Переменные создаются и уничтожаются как обычно:
Код

     mem:= TMemoryStream.Create;
  try

  finally
    mem.free;
  end;

За потоками Indy следит сам.
Утечки и все исключения мониторю в EurekaLog, собственно, утечек памяти она не фиксирует.
В процессе активного использования программы неумолимо растут затраты как оперативки, так и виртуальной памяти (мониторю в диспетчере винды). Оперативы у меня 1,5Гб, файл подкачки отключён полностью.
Оперативку периодически, по таймеру, высвобождаю процедурой:
Код

procedure FreeMemory;
var
MainHandle: THandle;
begin
    if Win32Platform = VER_PLATFORM_WIN32_NT then
    begin
    MainHandle := OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessID);
    SetProcessWorkingSetSize(MainHandle, DWORD(-1), DWORD(-1));
    CloseHandle(MainHandle);
    end;
end;

поставил таймер на 15 минут, к этому времени приложение успевает отхватить 250-300Мб озушки и почти столько же виртуальной памяти (при старте приложения используется всего - 10-15Мб). После вызова указанной процедуры оперативка высвобождается вся - приложение начинает занимать всего 1,5-3Мб, а вот виртуальная память растёт и дальше. И через определённое время, когда у винды виртуальная память кончается, моё приложение благополучно уничтожается (то-ли само вылетает, то-ли винда помогает)...
Так вот, как подчищать виртуальную память, на подобии оперативки?  И почему, вообще, память растёт, а не освобождается автоматом, коль уж утечек нет?

Автор: kami 18.5.2009, 21:16
Цитата(zedx @  18.5.2009,  20:35 Найти цитируемый пост)
 И почему, вообще, память растёт, а не освобождается автоматом, коль уж утечек нет?

Вполне возможно, что утечки не фиксируются благодаря тому, что используемая память высвобождается при завершении приложения. А при работе - просто добавляются новые данные.

Пример:
Код

var
  list:TList;

procedure TMyClass.FillList;
for i:=0 to x do
  begin
    MyMemStream:=TMemoryStream.Create;
    // заполняем данными, работаем
    list.Add(myMemStream); // вносим в список для (к примеру) передачи другому потоку
  end; // и забываем.


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

finalization
  for i:=0 to List.Count-1 do
    TObject(List[i]).Free;
  List.Free;
end.


Просмотрите свой код - наверняка что-то подобное есть.

Автор: zedx 18.5.2009, 21:37
Нет, блоков finalization в проге вообще нет, и по завершении тоже ничего не освобождаю. 
К тому же, если бы была ситуация как ты говиришь, то оперативка так глобально не освобождалась бы? 
Вот http://narod.ru/disk/8724130000/GeoCacher_20090515.rar.html эта прога с исходниками, может кто глянет опытным глазом? 

Автор: kami 18.5.2009, 21:48
Цитата(zedx @  18.5.2009,  21:37 Найти цитируемый пост)
Нет, блоков finalization в проге вообще нет, 

Да при чем тут finalization?
Это было приведено в качестве явного примера возрастания потребления памяти вплоть до вылета, без явных утечек.
освобождение может проходить где угодно - OnClose, Destroy и т.п. при завершении приложения.
Цитата(zedx @  18.5.2009,  21:37 Найти цитируемый пост)
может кто глянет опытным глазом

Ну, не могу сказать, что глаз у меня опытный, но посмотрим.

Автор: zedx 18.5.2009, 22:05
Цитата(kami @  18.5.2009,  21:48 Найти цитируемый пост)
освобождение может проходить где угодно - OnClose, Destroy и т.п. при завершении приложения.

Да я понял, но нету такого. При завершении приложения освобождаю мелочь - критические секции, и собственно, Indy - т.е. то, что создавалось при старте проги. А всё, что в процессе создаётся, в процессе же и уничтожаю... 
единственно, вот сюда приходит последняя переменная Resp.Body (TMemoryStream) которую я не уничтожаю - её по-идее должен инди освободить... все остальные стримы для этого потока, к этому моменту уже уничтожаются. 
Код
procedure TServer.Proxy_HTTPServerCommandGet(AContext: TIdContext;  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  FlatFile: TFlat;
  Resp: TResponse;
  FileType: byte;
begin
    if ARequestInfo.Host='localhost' then ARequestInfo.Host:=GCHost;

    try
        ActivateTimer;
        SplitToBeasyIcon(True);

        
        try

          FileType:=GE_FileType(ARequestInfo);

          case FileType of
           0..3: Resp:=GE_URL_Downlod(ARequestInfo);
           4:    Resp:=GEAuth_Downlod(ARequestInfo);
           5:    Resp:=dbRoot_Downlod(ARequestInfo);
           6:    Resp:=GEServer_Downlod(ARequestInfo);
           255:  Resp:=URL_Downlod(ARequestInfo);
          end;

          AResponseInfo.ResponseText:='';
          AResponseInfo.Server:='';

          AResponseInfo.Connection:='';
          AResponseInfo.ContentLength:=-1;
          AResponseInfo.RawHeaders.Text:='';
          if ContainsStr(Resp.Head,'Content-Type') then AResponseInfo.ContentType:='';

          AResponseInfo.CustomHeaders.Text:=Resp.Head;
          AResponseInfo.FreeContentStream:=true;                                            <- Indy должен сам уничтожить

          try
            
            if (Resp.Body<>nil) and (Resp.Body.Size<>0)  then
            AResponseInfo.ContentStream:=Resp.Body                                       <- последний стрим
            else
            begin
              AResponseInfo.RawHeaders.Text:='';
              if AnsNotFound then AResponseInfo.ResponseNo:=403
              else AResponseInfo.ResponseNo:=404;

              Resp.Body.Destroy;
              AResponseInfo.Server:='btfe';
              AResponseInfo.ContentType:='text/html';
              AResponseInfo.ContentText:='404: NOT FOUND';
            end


          except
             AResponseInfo.CustomHeaders.Text:='';
             AResponseInfo.ResponseNo:=404;
          end;

             AResponseInfo.FreeContentStream:=true;
        finally
          SplitToBeasyIcon(False);
        end;

    except on E: Exception do
      E.Message:='Proxy_HTTPServer_Error'+#13#10+E.Message;
    end;

end;


Автор: zedx 18.5.2009, 23:12
Самое интересное, что диспетчер задач винды, не обращает внимание на то, что происходит очистка озушки и в Хронологии использования файла подкачки (хотя повторюсь - файл подкачки отключён) - такая ровная восходящая линия, до момента выбрасывания/зависания проги... причём сама винда и проги не тормозят ничуть, т.е. опреративка-то свободна. А вот запуск фотошопа, в момент когда прога выжрала всю виртуалку, обломился, с ошибкой фотошопа - Файл подкачки слишком мал, для завершения операции. Странно это. 

Автор: CodeMonkey 18.5.2009, 23:27
Да ничего странного у вас нет. У вас либо нет утечки, но ошибка в логике (kami пример указал), либо утечка не памяти, а, скажем, объектов ядра (пример: создавать и не освобождать HBITMAP).

Если первое - попробуйте захавать как можно больше памяти, потом сделать дамп выделения (в этом вам поможет FastMM). Ткните наугад в карту памяти - с хорошей вероятностью получите виновника неявной утечки.
Если второе - то кроме mem-leak используйте другие утилиты - профайлеры ресурсов.

А EL какая версия? Я помню там баг когда-то был с детектом mem-leak-ов. Попробуйте ещё под FastMM или обновите EL до 6.0.20.

Добавлено @ 23:34
Да, кстати, после того, как разберётесь с проблемой, выкиньте вот это: SetProcessWorkingSetSize(MainHandle, DWORD(-1), DWORD(-1));

Автор: zedx 18.5.2009, 23:46
Экврика как раз 6.0.20.

Цитата(CodeMonkey @  18.5.2009,  23:27 Найти цитируемый пост)
Если второе - то кроме mem-leak используйте другие утилиты - профайлеры ресурсов

...новое для меня понятие, пошёл напрягать гугл smile

Автор: kami 19.5.2009, 00:06
При наличии 15 вызовов VirtualAlloc потребление памяти от вызовов этой ф-и достигло высот в 18353952 байт.
На 18-м программа скончалась.
MemProof упрямо говорит, что проблема в выделении памяти.
А именно:
function PutFileToCache => X:=TSaveX.Create(true); // показывает пальцем сюда.

И гораздо больше - 
просто вызов из function PutFlatFile => PutFileToCache. // и сюда
Который, кстати, идет в двух местах этой процедуры.

Автор: zedx 19.5.2009, 00:13
Цитата(kami @  19.5.2009,  00:06 Найти цитируемый пост)
MemProof упрямо говорит, что проблема в выделении памяти.А именно:function PutFileToCache => X:=TSaveX.Create(true); // показывает пальцем сюда.

т.е. поток не уничтожается?

вот этот класс
Код
TSaveX = class(TThread)
  private
    fTile: TBuf;
    fURL: string;
    fSize:int64;
    protected
      procedure Execute;override;
    public
      property Size:int64 read fSize write fSize;
      property URL:string read fURL write fURL;
      property Tile: TBuf read fTile write fTile;
  end;



и его процедура:
Код
procedure TSaveX.Execute;
var Xname,sURL:string;
id:integer;
Mem:TMemoryStream;
begin
    sURL:=URL;

    Mem:=TMemoryStream.Create;

    try
      Mem.Write(Tile,Size);
      Xname:=URLToXName(sURL);

      id:=dbxOpen(Xname);

      if ID<>-1 then
      begin
        EnterCriticalSection(dbxCache.FileSection[ID]);
        try
          dbXWrite(sURL,Mem,ID);
        finally
          LeaveCriticalSection(dbxCache.FileSection[ID]);
        end;
      end;
    finally
      Mem.Free;
    end;
end;


Автор: kami 19.5.2009, 00:26
Цитата(zedx @  19.5.2009,  00:13 Найти цитируемый пост)
т.е. поток не уничтожается?

1. Пройдитесь отладчиком - я не могу скомпилировать код, т.к. не пользуюсь инди.
А ковырять чужой код... ладно бы минимальный тестовый пример, в котором проявляется ошибка, а полный код smile

2. Скачайте себе профайлер и посмотрите сами что да как.

Автор: CodeMonkey 19.5.2009, 08:46
Цитата(zedx @  18.5.2009,  23:46 Найти цитируемый пост)
...новое для меня понятие, пошёл напрягать гугл

Посмотрите http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=943, http://www.automatedqa.com/products/aqtime/memproofusers/, http://www.automatedqa.com/products/aqtime/.

Автор: zedx 20.5.2009, 17:36
Поставил AQTime v6.20, интегрировался он в проект. Выбираю подозрительный модуль (с точки зрения утечки), правый клик мышью -> Profile xxx.pas В настройках выбрал профиль Allocation Profile. Запускаю, гоняю, вижу утечку в Диспетчере задач, закрываю прогу. AQT выдаёт мне репорт:
Код
Object  Name    Size    Root    Address    References To    Get#
VCL native memory.89637    380    False    0x06F23C44    0    1
...
VCL native memory.91587    380    False    0x06F24AE4    0    1
VCL native memory.102037    380    False    0x06F25304    0    1
VCL native memory.117764    380    False    0x06F25B24    0    1
Reserved Virtual Memory.2838    4096    False    0x06DB0000    0    1
Committed Virtual Memory.2839    4096    False    0x06DB0000    0    1
Reserved Virtual Memory.132092    5242880    False    0x08910000    0    1
Reserved Virtual Memory.123108    5242880    False    0x08010000    0    1

Очень много строчек VCL native memory - но размер мизерный, а вот 2 последние строчки: Reserved Virtual Memory - по 5 Мб - я так понимаю, это и есть утечка, т.к. строчек Committed после не наблюдается?
Не понимаю, как перейти теперь по этой инфе (Address 0x08010000) в исходники, на конкретную строку/процедуру, где эта утечка происходит... Что делать? 

Автор: zedx 20.5.2009, 23:11
Похоже, что нашел основную утечку (правда, методом тыка - по-этапно исключая из кода то, что можно отключить. Как находить утечки с AQTime так и не выяснил). Виновником оказался визуальный компонент от TMS: AdvStringGrid. Я использую его для динамического добавления строк+раскраска (причём всё это происходит из потока).
Помогло исключение из кода строчек: 
Код

AdvStringGrid1.Rows[RowData.Number]:=RowString;
AdvStringGrid1.RowColor[RowData.Number]:=HexToTColor(ReadyCl);

... и как мне теперь, интересно, строки писать?



Автор: kami 24.5.2009, 19:34
Цитата(zedx @  20.5.2009,  23:11 Найти цитируемый пост)
причём всё это происходит из потока

Нехорошо обращаться к визуальным компонентам из потока.
Сделать это хотя бы из метода синхронизации потока.
Поэтому и могут идти утечки.

Цитата(zedx @  20.5.2009,  23:11 Найти цитируемый пост)
и как мне теперь, интересно, строки писать?

А исходный код компонентов доступен?

Добавлено через 1 минуту и 25 секунд
Цитата(zedx @  20.5.2009,  23:11 Найти цитируемый пост)
 Как находить утечки с AQTime так и не выяснил

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

Автор: zedx 27.5.2009, 12:14
Цитата(kami @  24.5.2009,  19:34 Найти цитируемый пост)
Нехорошо обращаться к визуальным компонентам из потока.Сделать это хотя бы из метода синхронизации потока.Поэтому и могут идти утечки.

Вся работа с компонентом идёт из критической секции:
Код
 EnterCriticalSection(GUISect);
try
....
finally
LeaveCriticalSection(GUISect);
end;

Автор: kami 27.5.2009, 12:24
Цитата(zedx @  27.5.2009,  12:14 Найти цитируемый пост)
Вся работа с компонентом идёт из критической секции:Код

а код все равно выполняется в доп.потоке.

Добавлено через 1 минуту и 56 секунд
а должен - в основном

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)