Что может сделать мое 32-битное приложение, которое потребляет гигабайты физической памяти?
Сотрудник несколько месяцев назад упомянул мне, что одно из наших внутренних приложений Delphi, похоже, занимает 8 ГБ оперативной памяти. Я сказал ему:
Это невозможно
32-разрядное приложение имеет только 32-разрядное виртуальное адресное пространство. Даже если произошла утечка памяти, большая часть памяти, которую он может потреблять, составляет 2 ГБ. После этого выделения будут сбой (поскольку в виртуальном адресном пространстве не будет пустое пространство). И в случае утечки памяти виртуальные страницы будут заменены на файл подкачки, освобождая физическую память.
Но он отметил, что Windows Resource Monitor указал, что в системе доступно менее 1 ГБ ОЗУ. И хотя наше приложение использовало только 220 МБ виртуальной памяти: при закрытии он освободил 8 ГБ физической памяти.
Итак, я протестировал его
Я пропустил приложение на несколько недель, и сегодня я, наконец, решил проверить его.
Сначала я рассмотрю использование памяти перед закрытием приложения:
- рабочий набор (ОЗУ) 241 МБ
- общая используемая виртуальная память: 409 МБ
![введите описание изображения здесь]()
И я использовал Resource Monitor для проверки памяти, используемой приложением, и используемой общей оперативной памяти:
- виртуальная память, выделенная приложением: 252 МБ
- используемая физическая память: 14 ГБ
![введите описание изображения здесь]()
И затем использование памяти после закрытия приложения:
- используемая физическая память: 6.6 ГБ (на 7.4 ГБ ниже)
![введите описание изображения здесь]()
Я также использовал Process Explorer для просмотра разбивки физической памяти до и после. Единственное различие заключается в том, что 8 ГБ оперативной памяти действительно не было выполнено и теперь бесплатное:
| Item | Before | After |
|-------------------------------|------------|-----------|
| Commit Charge (K) | 15,516,388 | 7,264,420 |
| Physical Memory Available (K) | 1,959,480 | 9,990,012 |
| Zeroed Paging List (K) | 539,212 | 8,556,340 |
![введите описание изображения здесь]()
Примечание. Интересно, что Windows теряет время, мгновенно обнуляя всю память, вместо того, чтобы просто помещать ее в резервный список и нулевое при необходимости (так как запросы памяти должны быть удовлетворены).
Ни одна из этих вещей не объясняет, что оперативная память была делает (что вы делаете, просто сидите там! Что вы содержите!?)
Что в этой памяти!
В этой ОЗУ должно быть полезно что-то; он должен иметь некоторые цели. Для этого я обратился к SysInternals RAMMap. Он может распределять память по памяти.
Единственная подсказка, которую предоставляет RAMMap, заключается в том, что 8 ГБ физической памяти были связаны с чем-то вроде Session Private. Эти Сеансы Private не связаны с каким-либо процессом (т.е. Не мой процесс):
| Item | Before | After |
|------------------------|----------|----------|
| Session Private | 8,031 MB | 276 MB |
| Unused | 1,111 MB | 8,342 MB |
![введите описание изображения здесь]()
Я, конечно, ничего не делаю с EMS, XMS, AWE и т.д.
Что может произойти в 32-разрядном не-администраторском приложении, которое заставляет Windows выделять дополнительно 7 ГБ ОЗУ?
- Это не кэш обмениваемых элементов
- это не кеш SuperFetch
Это просто; потребляющей ОЗУ.
Сессия приватная
Единственная информация о "приватной сессии" - это сообщение в блоге с объявлением RAMMap:
Сессия приватная: Память, которая является частной для определенного сеанса. Это будет выше на серверах Host Host RDS.
Какое приложение это
Это 32-разрядное собственное приложение Windows (т.е. не Java, а не .NET). Поскольку он является родным приложением Windows, он, конечно же, активно использует Windows API.
Следует отметить, что я не просил людей отлаживать приложение; я надеялся, что разработчик Windows узнает, почему Windows может хранить память, которую я никогда не выделял. Сказав это, единственное, что изменилось в последнее время (за последние 2 или 3 года), которое может вызвать такую вещь, - это функция, которая снимает скриншот каждые 5 минут и сохраняет его в пользовательской папке %LocalAppData%
. Таймер срабатывает каждые пять минут:
QueueUserWorkItem(TakeScreenshotThreadProc);
И псевдокод метода потока:
void TakeScreenshotThreadProc(Pointer data)
{
String szFolder = GetFolderPath(CSIDL_LOCAL_APPDTA);
ForceDirectoryExists(szFolder);
String szFile = szFolder + "\" + FormatDateTime('yyyyMMdd"_"hhnnss', Now()) + ".jpg";
Image destImage = new Image();
try
{
CaptureDesktop(destImage);
JPEGImage jpg = new JPEGImage();
jpg.CopyFrom(destImage);
jpg.CompressionQuality = 13;
jpg.Compress();
HANDLE hFile = CreateFile(szFile, GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, null, CREATE_ALWAYS,
FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_ENCRYPTED, 0);
//error checking elucidated
try
{
Stream stm = new HandleStream(hFile);
try
{
jpg.SaveToStream(stm);
}
finally
{
stm.Free();
}
}
finally
{
CloseHandle(hFile);
}
}
finally
{
destImage.Free();
}
}
Ответы
Ответ 1
Скорее всего, где-то в вашем приложении вы выделяете системные ресурсы и не выпускаете их. Любой вызов WinApi, который создает объект и возвращает дескриптор, может быть подозреваемым. Например (будьте осторожны при работе с системой с ограниченной памятью - если у вас нет бесплатной 6 ГБ, страница будет плохо):
Program Project1;
{$APPTYPE CONSOLE}
uses
Windows;
var
b : Array[0..3000000] of byte;
i : integer;
begin
for i := 1 to 2000 do
CreateBitmap(1000, 1000, 3, 8, @b);
ReadLn;
end.
Это потребляет 6 ГБ памяти сеанса из-за размещения объектов растровых изображений, которые впоследствии не будут выпущены. Потребление памяти приложения остается низким, потому что объекты не создаются в куче приложения.
Однако, не зная больше о вашем приложении, очень сложно быть более конкретным. Вышесказанное является одним из способов продемонстрировать поведение, которое вы наблюдаете. Помимо этого, я думаю, вам нужно отлаживать.
В этом случае имеется большое количество выделенных объектов GDI - однако это необязательно является показательным, так как часто имеется большое количество небольших объектов GDI, выделенных в приложении, а не большое количество крупных объектов ( Например, IDE Delphi будет создавать > 3000 объектов GDI, и это не обязательно проблема).
![введите описание изображения здесь]()
В примере @Abelisto (в комментариях), напротив:
Program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
i : integer;
sr : TSearchRec;
begin
for i := 1 to 1000000 do FindFirst('c:\*', faAnyFile, sr);
ReadLn;
end.
Здесь возвращенные дескрипторы не относятся к объектам GDI, а скорее являются поисковыми ручками (которые относятся к общей категории объектов ядра). Здесь мы видим, что существует большое количество ручек, используемых процессом. Опять же, потребление памяти процесса низкое, но наблюдается значительное увеличение используемой памяти сеанса.
![введите описание изображения здесь]()
Аналогично, объектами могут быть объекты пользователя - они создаются вызовами типа CreateWindow
, CreateCursor
или установкой перехвата с помощью SetWindowsHookEx
. Список вызовов WinAPI, которые создают объекты и возвращают дескрипторы каждого типа, см. В разделе
Ручки и объекты: Категории объектов - MSDN
Это может помочь вам начать отслеживать проблему, сужая ее до типа вызова, который может вызвать проблему. Он также может быть поврежденным сторонним компонентом, если вы используете его.
Такой инструмент, как AQTime, может профилировать распределения Windows, но я не уверен, есть ли версия, поддерживающая Delphi5. Могут быть другие профилировщики распределения, которые могут помочь отслеживать это.