Почему память моей Delphi-программы продолжает расти? - PullRequest
12 голосов
/ 03 апреля 2010

Я использую Delphi 2009, в который встроен менеджер памяти FastMM4.

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

Используя процедуру CurrentMemoryUsage, приведенную в ответе spenwarr: Как получить память, используемую программой Delphi , я отобразил память, используемую FastMM4 во время обработки.

Кажется, что происходит то, что использование памяти растет после каждого процесса и цикла выпуска. e.g.:

1456 КБ используется после запуска моей программы без набора данных.

218 455 КБ используется после загрузки большого набора данных.

71 994 КБ после полной очистки набора данных. Если я выйду в этот момент (или в любой другой момент в моем примере), утечки памяти не поступят.

271,905 КБ используется после повторной загрузки того же набора данных.

125 443 КБ после полной очистки набора данных.

325 519 КБ используется после повторной загрузки того же набора данных.

179 059 КБ после полной очистки набора данных.

378,752 КБ используется после повторной загрузки того же набора данных.

Похоже, что объем используемой памяти моей программой увеличивается примерно на 53 400 КБ при каждом цикле загрузки / очистки. Диспетчер задач подтверждает, что это действительно происходит.

Я слышал, что FastMM4 не всегда высвобождает всю память программы обратно в операционную систему, когда объекты освобождаются, так что он может сохранять некоторую память, когда ей нужно больше. Но этот постоянный рост беспокоит меня. Поскольку утечек памяти не обнаружено, я не могу определить проблему.

Кто-нибудь знает, почему это происходит, если это плохо, и если я что-то могу или должен с этим поделать?


Спасибо, Дторп и Мейсон за ваши ответы. Вы заставили меня задуматься и попробовать то, что заставило меня понять, что я что-то упустил Требовалась детальная отладка.

Как оказалось, все мои структуры были должным образом освобождены при выходе. Но освобождения памяти после каждого цикла во время выполнения не было. Он накапливал блоки памяти, которые обычно вызывали бы утечку, которую можно было бы обнаружить на выходе, если бы моя очистка на выходе была неправильной - но это было.

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

На этот вопрос дан ответ. Спасибо за вашу помощь.

Ответы [ 3 ]

25 голосов
/ 03 апреля 2010

Утилита CurrentMemoryUsage, с которой вы связались, сообщает о размере рабочего набора вашего приложения. Рабочий набор - это общее количество страниц адресного пространства виртуальной памяти, сопоставленных с адресами физической памяти. Однако некоторые или многие из этих страниц могут содержать очень мало фактических данных. Таким образом, рабочий набор является «верхней границей» того, сколько памяти использует ваш процесс. Он указывает, сколько адресного пространства зарезервировано для использования, но не указывает, сколько фактически зафиксировано (фактически находится в физической памяти) или сколько зафиксированных страниц фактически используется вашим приложением.

Попробуйте это: после того, как вы увидите, как размер рабочего набора увеличивается после нескольких тестовых прогонов, сверните главное окно вашего приложения. Скорее всего, вы увидите значительное снижение размера рабочего набора. Зачем? Поскольку Windows выполняет вызов SetProcessWorkingSetSize (-1), когда вы минимизируете приложение, которое отбрасывает неиспользуемые страницы и сокращает рабочий набор до минимума. ОС не делает этого, пока окно приложения имеет нормальный размер, поскольку слишком частое уменьшение размера рабочего набора может ухудшить производительность, заставив данные перезагружаться из файла подкачки.

Для более подробной информации: ваше Delphi-приложение распределяет память довольно небольшими порциями - строка здесь, класс там. Среднее выделение памяти для программы обычно составляет менее нескольких сотен байтов. Трудно эффективно управлять такими небольшими распределениями в масштабе всей системы, поэтому операционная система этого не делает. Он эффективно управляет большими блоками памяти, особенно при размере страницы виртуальной памяти 4 КБ и минимальном размере диапазона адресов 64 К виртуальной памяти.

Это представляет проблему для приложений: приложения обычно выделяют небольшие порции, но ОС выделяет память довольно большими порциями. Что делать? Ответ: перераспределить.

Диспетчер памяти библиотеки времени выполнения Delphi и менеджер замещающей памяти FastMM (и библиотеки времени выполнения практически всех других языков или наборов инструментов на планете) существуют для того, чтобы сделать одно: разделить большие блоки памяти из ОС на более мелкие блоки используется приложением. Для отслеживания того, где находятся все маленькие блоки, насколько они велики, и были ли они «просочились», требуется также некоторая память - так называемые накладные расходы.

В ситуациях интенсивного выделения / освобождения памяти могут быть ситуации, когда вы освобождаете 99% от выделенного, но размер рабочего набора процесса уменьшается, скажем, на 50%. Зачем? Чаще всего это вызвано фрагментацией кучи: один маленький блок памяти все еще используется в одном из больших блоков, которые менеджер памяти Delphi получил от ОС и разделил внутри. Внутренний счетчик используемой памяти невелик (скажем, 300 байт), но поскольку он мешает администратору кучи освободить большой блок, который находится в ОС, вклад рабочего набора в этот маленький 300-байтовый блок больше похож на 4k (или 64к в зависимости от того, виртуальные ли это страницы или виртуальное адресное пространство - не могу вспомнить).

В операции с интенсивным использованием памяти, включающей мегабайты небольших выделений памяти, фрагментация кучи является очень распространенной, особенно если выделение памяти для вещей, не связанных с операцией с интенсивным использованием памяти, происходит одновременно с большой задачей. Например, если при выполнении операции с базой данных объемом 80 МБ также выводится состояние в список по мере его продвижения, строки, используемые для сообщения о состоянии, будут разбросаны в куче среди блоков памяти базы данных. Когда вы освобождаете все блоки памяти, используемые вычислениями базы данных, строки списка все еще там (используются, не потеряны), но они разбросаны повсюду, потенциально занимая целый большой блок ОС для каждой маленькой строки.

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

1 голос
/ 03 апреля 2010

Какой набор данных вы используете? Если он полностью реализован в Delphi (без обращения к другому коду с другим менеджером памяти, например Midas), вы можете попытаться сознательно утечь набор данных.

Я предполагаю, что ваш набор данных находится в форме, и он автоматически освобождается, когда форма очищает свои компоненты. Попробуйте ввести MyDataset := nil; в форму OnDestroy. Это обеспечит утечку набора данных, а также всего, что принадлежит этому набору данных. Попробуйте сделать это после загрузки один раз и снова после загрузки дважды, сравните отчеты об утечках и посмотрите, даст ли это что-нибудь полезное. *

0 голосов
/ 19 декабря 2010

Вы наполовину утечка памяти; очевидно. Во время работы программы происходит утечка памяти, но когда вы закрываете программу, ваш набор данных освобождается должным образом, поэтому FastMM (по праву) не сообщает об этом.

См. Подробности: Моя программа никогда не освобождает память обратно. Почему?

...