Как malloc память ест

от автора

Нет, здесь не будет ничего из серии «Аааа, я сделал malloc (new), и забыл сделать free (delete)!»
Здесь будет нечто изощренное: мы будем отрезать кусочки памяти по чуть-чуть, прятать их в укромное место… А когда операционная система заплатит выкуп скажет «Хватит!», мы попробуем вернуть все обратно. Казалось бы, простейшая операция выделения и освобождения памяти — ничего не предвещает беды.
Тем кому интересно как уничтожить забить память — прошу под хабракат

Немножко предыстории

По долгу службы приходится много работать с большими буфферами памяти (представьте себе изображение 5000×40000 пиксел). Порой (из-за фрагментации) не получается выделять непрерывный кусок памяти для всего. Поэтому был написан некоторый менеджер памяти, который выделял сколько есть, возможно, несколькими кусками. Естественно, менеджер памяти должен как выделять, так и удалять. Тогда была обнаружена следующая интересная вещь: 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 allocated\n");   printf("Hit something...\n");   getchar();   for (int i=0; i<size; i++)   {     free(buf[i]);   }   free(buf);   printf("Hit something...\n");   printf("Memory freed\n");   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.
Не стоит прямо так сразу доверять давно проверенным функциям, в них может крыться подвох.

ссылка на оригинал статьи http://habrahabr.ru/post/158347/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *