Cocoa 64-битная двоичная память утечки? (освобождение NSData не освобождает память)
Я играл некоторое время с разными сборками моего приложения, и там, кажется, происходят странные вещи:
мое приложение имеет 5-миллиметровый простоя. при сохранении загрузки файловой памяти в размере файла. после загрузки зарезервированная память должна быть освобождена. теперь есть различия в сборках (gc = сборщик мусора):
- 32bit i386 no-GC: вся память мгновенно освобождается.
- 32bit i386 GC: почти вся память мгновенно освобождается. остальные через некоторое время.
- 64-битный x86_64 no-GC: минимальная память освобождается. как 10%
- 64bit x86_64 GC: память вообще не освобождается. память хранится в течение нескольких часов. (активность mon)
Я использую LLVM с CLANG. я все время работал с инструментами и проверял наличие утечек/зомби/и т.д. и все кажется чистым. (приложение довольно простое.)
Есть ли объяснение этого поведения?
Update:
Что-то странное. Я задал эту проблему:
Я загружаю файл 20mb в NSData и освобождаю его. Я делаю это без всякой сборки мусора. Код:
NSData *bla = [[NSData alloc] initWithContentsOfFile:@"/bigshit"];
[bla release];
Когда я создаю для i386 32bit, 20mb выделяются и освобождаются. Когда я переключаю сборку на 64-битный x86_64, релиз ничего не делает. Осталось 20 мб.
top pic 32bit lower 64 http://kttns.org/zguxn
Нет никакой разницы между двумя приложениями, кроме того, что верхняя часть построена для 32-битной, а нижняя - 64 бит. Не работает GC. (При включенной GC появляется такая же проблема.)
Обновление 2:
Такое же поведение наблюдается при создании нового приложения cocoa с нуля только с верхним кодом в applicationDidFinishLaunching:. В 64-битном режиме память не отпущена. i386 работает как ожидалось.
Аналогичная проблема возникает с NSString вместо NSData. Он также появляется, когда я загружаю 64-битное ядро. (Удерживание 64 при запуске.)
OS - 10.6.0
Ответы
Ответ 1
Сначала используйте инструмент Instrument Object Graph, чтобы убедиться, что память больше не считается используемой; не имеет значения удержания или сильной ссылки.
Если он больше не используется, тогда память зацикливается просто потому, что вы не достигли порога, в котором заботится коллекционер.
Однако это утверждение:
64-бит x86_64 no-GC: минимальная память освобождена. как 10%
Меня насторожило. В частности, если ваш код предназначен для работы в не-GC - с сохранением/выпуском, то либо (a) у вас есть утечка памяти, и, если вы используете CFRetain или какой-то глобальный кеш, который может повлиять на GC или (b ) вы не используете правильные инструменты, чтобы выяснить, есть ли у вас утечка памяти.
Итак, как вы определяете, что вы пропускаете память?
Обновить; вы используете Activity Monitor для мониторинга RSIZE/VSIZE процесса. На самом деле это не говорит вам ничего полезного, кроме "мой процесс растет со временем".
Скорее всего, нет (я не смотрел источник), этот код:
NSData *bla = [[NSData alloc] initWithContentsOfFile:@"/bigpoop"];
В результате процесс 20MB будет mmap()
'd. Во всем этом не задействовано распределение стиля malloc(). Вместо этого ОС передает 20 МБ смежного адресного пространства в ваш процесс и отображает содержимое файла в него. Когда вы читаете содержимое NSData, это приведет к ошибке страницы в файле, когда вы идете.
Когда вы отпускаете bla
, отображение уничтожается. Но это не означает, что подсистема VM уменьшит адресное пространство вашего приложения на 20 МБ.
Итак, вы сжигаете кучу адресного пространства, но не фактическую память. Поскольку ваш процесс составляет 64 бита, адресное пространство представляет собой почти бесконечный ресурс, и для использования адресов очень мало затрат, поэтому причина, по которой ОС реализована таким образом.
т.е. нет утечки, и ваше приложение ведет себя корректно, GC или нет.
Это распространенное заблуждение, и, таким образом, у него был вопрос.
Ответ 2
Сборщик мусора не обязательно немедленно освобождает память.
В случае сборщика мусора Objective-C вы можете отправить Cocoa объект сборщика мусора a collectIfNeeded
, чтобы предположить, что сейчас может быть подходящее время для выполнения какой-либо коллекции или collectExhaustively
, чтобы заказать его немедленно начните собирать все и вся мусор (но даже это прерывается). См. документы.
Ответ 3
У меня очень похожая проблема в iPhoneOS 3.2, и я действительно не думаю, что память исправляется - я в конечном итоге вызываю предупреждения памяти. Есть небольшой шанс, что я упустил ошибку, но я был очень основателен.
Я использую NSKeyedUnarchiver unarchiveObjectWithFile: для загрузки пользовательского объекта, который содержит одну большую NSData и другой гораздо меньший объект. Вызывается метод dealloc в моем пользовательском объекте, вызывается объект NSData, его значение сохраняется ранее. Физическая память не уменьшается на любую величину, не говоря уже о частичном размере NSData, и с предупреждениями памяти повторения надежно генерируются: у меня есть тест, пока я не получил предупреждения уровня 2. = (
Перед выпуском:
(gdb) p (int) [(NSData *) pastItsWelcomeData saveCount]
$ 1 = 1
После выпуска:
(gdb) p (int) [(NSData *) pastItsWelcomeData saveCount]
Target не отвечает на этот селектор сообщений.