"Личная память" не освобождается после перехвата bad_alloc, несмотря на уничтожение объекта
Объект пытается выделить больше памяти, чем разрешенное виртуальное адресное пространство (2 ГБ на win32). std::bad_alloc
пойман и объект освобожден. Использование памяти процесса падает, и процесс должен продолжаться; однако любое последующее выделение памяти не выполняется с другим std::bad_alloc
, Проверка использования памяти с помощью VMMap показала, что динамическая память, кажется, освобождается, но на самом деле она помечена как частная, не оставляя свободного места. Единственное, что нужно сделать, это выйти и перезапустить. Я бы понял проблему фрагментации, но почему процесс не может вернуть память после выпуска?
Объект является QList
из QList
s. Приложение многопоточное. Я мог сделать маленький репродуктор, но я мог воспроизвести проблему только один раз, в то время как в большинстве случаев репродукции могут снова использовать освобожденную память.
Qt делает что-то подлое? Или, может быть, Win32 задерживает выпуск?
2 ответа
Ответ Мартина Дрэба поставил меня на правильный путь. Изучая распределение кучи, я обнаружил это старое сообщение, в котором разъясняется, что происходит:
Проблема здесь в том, что блоки свыше 512 КБ являются прямыми вызовами к VirtualAlloc, а все остальное меньше этого размера выделяется из сегментов кучи. Плохая новость заключается в том, что сегменты никогда не освобождаются (полностью или частично), поэтому, когда вы берете все адресное пространство небольшими блоками, вы не можете использовать их для других куч или блоков свыше 512 К.
Проблема не в Qt, а в Windows; Я мог бы наконец воспроизвести это с простой std::vector
массивов символов Распределитель кучи по умолчанию оставляет сегменты адресного пространства неизменными даже после явного освобождения соответствующего распределения. Отношение состоит в том, что процесс может снова запрашивать буферы аналогичного размера, и менеджер кучи сэкономит время на повторное использование существующих сегментов адресов вместо сжатия старых для создания новых.
Обратите внимание, что это не имеет никакого отношения к количеству физической или виртуальной памяти. Только адресное пространство остается сегментированным, хотя эти сегменты свободны. Это серьезная проблема на 32-битных архитектурах, где адресное пространство составляет всего 2 ГБ (может быть 3).
Вот почему память была помечена как "частная", даже после освобождения, и, очевидно, не использовалась тем же процессом для mallocs среднего размера, даже если выделенная память была очень низкой.
Чтобы воспроизвести проблему, просто создайте огромный вектор фрагментов размером менее 512 КБ (они должны быть выделены с помощью new или malloc). После того, как память заполнена и затем освобождена (независимо от того, достигнут ли лимит и обнаружено ли исключение или память просто заполнена без ошибок), процесс не сможет выделить что-то большее, чем 512 КБ. Память свободна, она назначена одному и тому же процессу ("private"), но все сегменты слишком малы.
Но есть и худшие новости: по-видимому, нет способа принудительно уплотнить сегменты кучи. Я пытался с этим и с этим, но безуспешно; нет точного эквивалента POSIX fork()
(см. здесь и здесь). Единственное решение состоит в том, чтобы сделать что-то более низкое, например, создать частную кучу и уничтожить ее после небольших выделений (как предложено в приведенном выше сообщении) или реализовать пользовательский распределитель (там может быть какое-то коммерческое решение). И то, и другое невозможно для большого существующего программного обеспечения, где самое простое решение - закрыть процесс и перезапустить его.
Как я понимаю вашу проблему, вы выделяете большие объемы памяти из кучи, которая в какой-то момент перестает работать. Освобождение памяти обратно к куче процесса не обязательно означает, что менеджер кучи фактически освобождает виртуальные страницы, которые содержат только свободные блоки кучи (из-за соображений производительности). Итак, если вы попытаетесь выделить виртуальную память напрямую (VirtualAlloc
или же VirtualAllocEx
), попытка не удалась, поскольку почти вся память используется диспетчером кучи, который не имеет возможности узнать о вашей попытке прямого выделения.
Ну, что вы можете сделать с этим. Вы можете создать свою собственную кучу (HeapCreate
) и ограничить его максимальный размер. Это может быть довольно сложно, так как вам нужно убедить Qt использовать эту кучу.
При выделении большого объема памяти я рекомендую использовать VirtualAlloc
а не куча функций. Если запрошенный размер> 512 КБ, фактически, программа для работы с кучей использует VirtualAlloc
чтобы удовлетворить ваш запрос. Однако я не знаю, действительно ли он освобождает страницы, когда вы освобождаете регион, или начинает ли он использовать его для удовлетворения других запросов на выделение кучи.