Нет, здесь не будет ничего из серии «Аааа, я сделал malloc (new), и забыл сделать free (delete)!»
Здесь будет нечто изощренное: мы будем отрезать кусочки памяти по чуть-чуть, прятать их в укромное место… А когда операционная система заплатит выкуп скажет «Хватит!», мы попробуем вернуть все обратно. Казалось бы, простейшая операция выделения и освобождения памяти — ничего не предвещает беды.
Тем кому интересно как уничтожить забить память — прошу под хабракат
Немножко предыстории
По долгу службы приходится много работать с большими буфферами памяти (представьте себе изображение 5000x40000 пиксел). Порой (из-за фрагментации) не получается выделять непрерывный кусок памяти для всего. Поэтому был написан некоторый менеджер памяти, который выделял сколько есть, возможно, несколькими кусками. Естественно, менеджер памяти должен как выделять, так и удалять. Тогда была обнаружена следующая интересная вещь: Task Manager после освобождения показывает уровень использования памяти такой же как и до выделения блока. Однако никакой новый блок памяти в программе не может быть выделен. Использование средств анализа виртуальной памяти (VMMap от Марка Русиновича) показывает, что память остается занята несмотря на ее освобождение в коде и несмотря на показания TM.
Анализ
Напишем быстренько какую-нибудь программку, которая выделяет и освобождает память. Что нибудь такое, сродни «Hello, World!»:
int main(void)
{
const int blockCount = 1024;
const int blockSize = 1024*1024;
char **buf;
printf("Hit something...n");
getchar();
buf = (char**)malloc(blockCount*sizeof(char*));
for (int i=0; i<size; i++)
{
buf[i] = (char*)malloc(blockSize*sizeof(char));
}
printf("Memory allocatedn");
printf("Hit something...n");
getchar();
for (int i=0; i<size; i++)
{
free(buf[i]);
}
free(buf);
printf("Hit something...n");
printf("Memory freedn");
getchar();
return 0;
}
Несложными подсчетами можно убедиться, что программа должна выделить 1 ГБ памяти, а затем все освободить. После запуска и проверки вся память освобождается. Хм, кажется, система шантажу не поддается. Впрочем, мы резали большие куски.
Теперь возьмем и немножко поправим исходный код:
const int blockSize = 520133 //К примеру...;
В этом случае мы получим, что память выделилась, но не освободилась:
До «Memory freed»:
После «Memory freed»:
Пытливый ум программиста не остановился на достигнутом! Я начал искать пороговое значение, при котором возникает такой эффект. После недолгого бинарного подбора выяснилось, что при размере равном
- 520168 байт и выше — освобождение проходит нормально
- 520167 байт и ниже — имеем описанную проблему
Забегая вперед скажу, что никаким образом подобное значение порога я объяснить не смог. Оно не делится даже на 1024!
Возможное объяснение
После длительных бдений за гуглом и изучения форумов я пришел к следующим выводам.
Оказывается что после выделения памяти с помощью функций malloc/new в том случае если выделяется маленький кусок, то память не освобождается функциями free/delete, а переходит из разряда committed в разряд reserved. И если мы обращаемся к данной памяти тут же после удаления (по всей видимости в рамках одного хипа), то она может быть выделена повторно. Однако при попытке выделить память из другого класса (либо статической функции) мы получим исключение — не достаточно памяти. По всей видимости при выделении памяти из статической функции память выделяется не в том же хипе, что и при обычном выделении изнутри класса приложения.
В результате после создания большого блока памяти (из маленьких кусочков) мы исчерпываем память и не можем в дальнейшем выделить себе еще немножко ну хоть чуть-чуть! памяти.
Неправильное решение
Использование функций VirtualAlloc/VirtualFree (MSDN) решает данную проблему, память полностью возвращается процессу после использования (ключ MEM_RELEASE), однако при использовании VirtualAlloc происходит сильная фрагментация памяти, и где-то 800Мб памяти не доступно для использования, т.к. максимальный размер свободного блока — 28Кб. Классический malloc в этом плане работает лучше, т.к. там есть некоторый дефрагментатор.
Окончательное решение
Нашел стороннюю реализацию malloc и free (как выясняется, широко известную в узких кругах), которая имеет классический недостаток дефрагментации памяти, но в месте с тем освобождает полностью память после использования. Плюс еще и заметно быстрее работает.
Для любопытствующих и жаждущих имеется ссылка
Ремарки
Под ОС *NIX (Ubuntu, Debian, CentOS) повторить проблему не удалось)
Под ОС Windows проблема была воспроизведена на Windows Server 2003 x64, Windows 7 x64, Windows XP x32.
Не стоит прямо так сразу доверять давно проверенным функциям, в них может крыться подвох.
Автор: serenheit