Если / когда освобождается освобожденная память кучи?

я запускал тесты памяти на ночь во встроенной системе Linux. Используя vmstat, я заметил, что свободная память неуклонно уменьшается с течением времени. По словам некоторых smaps анализ procfs, куча одного процесса растет примерно с той же скоростью. Я заподозрил утечку памяти и нашел несколько мест в коде, где new и delete регулярно используются. Однако я не видел new вызовы без сопоставления delete звонки.

I снова запустил тест памяти, и сегодня утром очистил кэши памяти со следующим вызовом

echo 3 > /proc/sys/vm/drop_caches

свободная память, перечисленная в vmstat, снизилась до значения, близкого к моменту запуска теста.

ядро регулярно восстанавливает неиспользуемые страницы кучи? Если да, то есть ли другие времена, кроме того, что выше, что это делается? Возможно, когда свободная память опускается ниже определенного порога?

4 ответов


как говорили другие, процесс обязан вернуть память ядру.

обычно есть 2 способа выделить память: если вы malloc()/new блок памяти выше определенного размера, память выделяется из ОС через mmap() и перевернулся, как только освободился. Меньшие блоки выделяются путем увеличения области данных процесса путем смещения sbrk границы вверх. Эта память освобождается только в том случае, если блок определенного размера свободен в конце этого сегмент.

например: (псевдо-код, я не очень хорошо знаю C++)

a = new char[1000];
b = new char[1000];

карта памяти:

---------------+---+---+
end of program | a | b |
---------------+---+---+

если вы бесплатно a теперь у вас есть отверстие в середине. Она не свободна, потому что ее нельзя освободить. Если вы свободны b память процесс может или не может быть уменьшена; неиспользованный остаток возвращается в систему.

тест с такой простой программой, как

#include <stdlib.h>

int main()
{
    char * a = malloc(100000);
    char * b = malloc(100000);
    char * c = malloc(100000);
    free(c);
    free(b);
    free(a);
}

приводит к strace выход как

brk(0)                                  = 0x804b000
brk(0x8084000)                          = 0x8084000
brk(0x80b5000)                          = 0x80b5000
brk(0x809c000)                          = 0x809c000
brk(0x8084000)                          = 0x8084000
brk(0x806c000)                          = 0x806c000

показывает, что brk значение сначала увеличивается (для malloc()), а затем снова уменьшился (для free()).


ядро будет восстанавливать кэшированные страницы памяти, когда оно в них нуждается, т. е. когда в противном случае у системы закончится память. будут ли страницы памяти из кучи процессов (free store) когда-либо возвращены в ОС по усмотрению менеджера памяти процесса, в этом случае new/delete реализация в библиотеке C++. Это полностью добровольная операция, к которой ядро не имеет никакого отношения.

из того, что drop_caches сделал трюк, вы можете сделать вывод то, что это был кэш ядра, а не куча процесса, которая заполняла память. Используйте free команда, чтобы узнать, сколько памяти на самом деле доступно для использования в приложении, esp. the -/+ buffers/cache линия отчета.


вызов delete в вашей программе заставляет память возвращаться в диспетчер памяти,который является частью вашей программы выполнения. В принципе, это можно было бы написать так, чтобы вернуть освобожденную память в ОС, но я был бы удивлен, если бы это произошло. Скорее переработанная память хранится в стороне для последующих вызовов new.

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


пользовательские вызовы malloc и free (или new и delete), насколько мне известно, никогда не возвращают больше не используемые страницы в O/S. Вместо этого они просто помнят, какая память была освобождена, так что если вы сделаете malloc/new размера, который может быть удовлетворен ранее освобожденной памятью, то он будет использовать это, а не идти в O/S и использовать sbrk для получения большего объема памяти.

таким образом этот код:

for (;;)
{
    struct { char data[200 * 1024 * 1024] } HugeBuffer;
    HugeBuffer *buff = new HugeBuffer;
    delete buff;
}

выделит 200 Мб один раз, а затем просто стабильно использовать эту память навсегда. Он перейдет к O/S один раз в исходном распределении, а затем зациклится на пользовательском пространстве.