i’ve been playing some time with different builds of my application and there seem strange things to happen:
my app has a 5mb idle footprint. when uploading a file memory in size of the file is reserved. after the upload the reserved memory should be freed. now there are differences in the builds (gc = garbage collector):
- 32bit i386 no-GC: all memory is freed instantly.
- 32bit i386 GC: almost all memory is freed instantly. the rest some time later.
- 64bit x86_64 no-GC: minimal memory is freed. like 10%
- 64bit x86_64 GC: no memory at all is freed. the memory stays reserved for hours. (activity mon)
i’m using LLVM with CLANG. i have been running today instruments all the time and was checking for leaks/zombies/etc. and everything seems to be clean. (the app is rather simple.)
is there an explanation for this behavior?
Update:
That’s some weird stuff. I’ve boiled the problem to this:
I load a 20mb file into a NSData and release it. I’m doing this without any garbage collection enabled. The code is:
NSData *bla = [[NSData alloc] initWithContentsOfFile:@"/bigshit"];
[bla release];
When I build for i386 32bit the 20mb are allocated and released. When I switch the build to 64bit x86_64 the release does nothing. The 20mb stay allocated.
upper pic 32bit lower 64 http://kttns.org/zguxn
There is no difference between the two apps except that the upper one is built for 32bit and the lower one 64bit. There is no GC running. (With GC enabled the same problem appears.)
Update 2:
The Same behavior can be observed when I create a new cocoa app from scratch with only the upper code in applicationDidFinishLaunching:. In 64bit mode the memory is not released. i386 works as expected.
The same problem appears with NSString instead of NSData. It also appears when I boot the 64bit kernel. (Holding 64 at startup.)
OS is 10.6.0
First, use Instrument’s Object Graph instrument to verify that the memory is no longer considered to be in use; does not have a retain count or a strong reference somewhere.
If it is no longer in use, then the memory is sticking around simply because you haven’t hit the threshold at which the collector cares.
However, this statement:
Makes me wary. Specifically, if your code is designed to work in non-GC — with retain/release — then either (a) you have a memory leak and, if using CFRetain or some kind of global cache, that might impact GC or (b) you aren’t using the right tools to figure out whether or not you have a memory leak.
So, how are you determining that you are leaking memory?
Update; you are using Activity Monitor to monitor the RSIZE/VSIZE of the process. This won’t actually tell you anything useful beyond “is my process growing over time”.
More likely than not (I haven’t looked at the source), this code:
Will cause the 20MB file to be
mmap()‘d in to the process. There isn’t a malloc() style allocation involved at all. Instead, the OS hands 20MB of contiguous address space to your process and maps the file’s contents into it. As you read the contents of the NSData, it’ll page fault in the file as you go.When you release
bla, the mapping is destroyed. But that doesn’t mean that the VM subsystem is going to reduce your application’s address space by 20MB.So, you are burning up a bunch of address space, but not actual memory. Since your process is 64 bits, address space is pretty much an infinite resource and there is very little cost to using addresses, thus the reason why the OS is implemented this way.
I.e. there is no leak and your app is behaving correctly, GC or no.
This is a common misconception and, thus, star’d the question.