How does the XCode Instrument Leak tool figure out if an object is a leak or just something not released yet?
I'm pretty new to Objective C, the leak tool detects a leak in the code I work with. But the code looks sound to me. So just wondering how much can I trust this tool?
A "leak" as an object that's still allocated, but your application no longer has a reference
pointing to that object. Since you no longer have a reference, there's no way you will be able to release the object, thus it's a leak.
As the leaks(1) man page says:
leaks identifies leaked memory -- memory that the application has allocated, but has been lost and cannot be freed. Specifically, leaks examines a specified process's memory for values that may be pointers to malloc-allocated buffers. Any buffer reachable from a pointer in writable memory, a register,
or on the stack is assumed to be memory in use. Any buffer reachable from a pointer in a reachable
malloc-allocated buffer is also assumed to be in use. The buffers which are not reachable are leaks;
the buffers could never be freed because no pointer exists in memory to the buffer, and thus free()
could never be called for these buffers
You might also want to look into the ObjectAlloc tool in Instruments.
Related
I'm learning how memory management works in Objective-C. From what I've learned, objects that are marked autorelease will be added to the enclosing NSAutoreleasePool and be released whenever the pool is released/drained.
To try out my new knowledge I created some tests; Creating 1 million objects used ~17mb, creating the same objects but immediately releasing them used ~1mb. However, I cannot get NSAutoreleasePool to work, using the code below still uses ~17mb and I do not understand why.
#include <objc/objc-runtime.h>
#include <stdio.h>
int main(void) {
Class NSAutoreleasePool = objc_getClass("NSAutoreleasePool");
Class Object = objc_getClass("NSObject");
SEL new = sel_registerName("new");
SEL drain = sel_registerName("drain");
SEL autorelease = sel_registerName("autorelease");
id pool = objc_msgSend(NSAutoreleasePool, new);
for (int i = 0; i < 1e6; i++) {
id obj = objc_msgSend(Object, new);
objc_msgSend(obj, autorelease);
}
objc_msgSend(pool, drain);
printf("OK\n");
getchar();
return 0;
}
When you release each object immediately after creating it, there's never more than one object in existence at a time. So the runtime can recycle the same memory for each new object. The runtime only has to ask the operating system for enough memory to hold that one object.
When you autorelease each object after creating it, each object lives until the autorelease pool is drained, so you end up with 1,000,000 objects existing simultaneously. The runtime has to ask the operating system for enough memory to hold all of the objects. And since the runtime doesn't know in advance how many objects you intend to create, it asks the operating system for chunks of memory incrementally. Maybe it asks for 1 MiB, and when that 1 MiB is exhausted, it asks for another 1 MiB, and so on.
Generally, when your program frees some memory (in this case because it destroys an object), the runtime doesn't return that memory to the operating system. It keeps the memory available so that it can reuse it the next time you create an object.
The operating system knows how much memory it has given to your process, but it doesn't know which parts of that memory are (within the process) consider “in use” and which parts are considered “free”.
Activity Monitor and top both ask the operating system for information about your process. So they only know what the operating system knows, which is how much memory has been given to your process. They can't know how much of that memory is in use and how much is free.
If you want a more accurate picture of how much memory is in use by live objects in your program, you need to use a more invasive tool. The Instruments program, which comes with Xcode, can show you how much memory is in use by “live allocations” using the Allocations instrument.
Here's what the Allocations instrument shows, when I run your program under it for about three seconds:
So you can see that the Allocations instrument detected a total of 1,001,983 “transient” allocations. A transient allocation is a chunk of memory that was allocated and then freed before Instruments stopped recording.
You can also see that the “persistent” bytes allocated (which is memory allocated and not freed by the time Instruments stopped recording) is 506 KiB, but the total bytes allocated (including bytes later freed) is 23.5 MiB.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What are the differences between free, dealloc, release, and autorelease?
I want to get rid of my allocated memory used in my app. I know I need to use release but what is the difference between free() and release? Are they the same?
free() is part of the C standard library, so it's a function. It immediately frees the allocated memory obtained using malloc(), so it must be passed a pointer that is allocated by malloc(), else it invokes undefined behavior.
- release is a method (as opposed to a function) of the NSObject class. It does not immediately free memory; it only decrements an object's reference count by one. It then also checks for it being 0 - if it is zero, it invokes - dealloc (which is usually overridden by a subclass to free memory allocated by the constructor method, - init or free() memory allocated by malloc()).
So they are not the same at all, do not even attempt to use them interchangeably!
The more interesting part is how free works: (and in this direction, malloc too can be understood better)
In many malloc/free implementations, free does normally not return the memory to the operating system (or at least only in rare cases). The reason is, that you will get gaps in your heap and thus it can happen, that you just finish of your 2 or 4 GB of virtual memory with gaps. This should be avoided of course, since as soon as the virtual memory is finished, you will be in really big trouble. The other reason of course is, that the OS can only handle memory chunks that are of a specific size and alignment. To be specific: Normally the OS can only handle blocks that the virtual memory manager can handle (most often multiples of 512 Bytes eg. 4KB).
So returning 40 Bytes to the OS will just not work. So what does free do?
Free will put the memory block in its own free block list. Normally it also tries to meld together adjacent blocks in the address space. The free block list is just a circular list of memory chunks which have of course some admin data in the beginning. This is also the reason, why managing very small memory elements with the standard malloc/free is not efficient. Every memory chunk needs additional data and with smaller sizes more fragmentation happens.
The free-list is also the first location, malloc looks for a new chunk of memory when needed. It is scanned before it calls for new memory from the OS. When a chunk is found that is bigger then the needed memory, it is just divided into two parts. One is returned to caller, the other is put back into the free list.
Release:Cocoa uses certain naming conventions. Anything that starts with alloc, new, or copy returns something with a retainCount of 1 and you are required to release.When release is called the reatinCount decrements by 1
I followed this video tutorial for detecting memory leaks using Instruments with Xcode 4.3.2.
As you can see from the video, the creator gets a lot of useful feedback on the type of object that was leaked etc.
When I run instruments, I detect a few memory leaks but don't get much useful feedback on them:
What does it mean "Root Leaks"? Why is there no more useful information like in the screen above?
Is this something I can fix?
I'm using ARC within my app - does that effect Instruments finding memory leaks in any way?
A root leak can be one of two things. It can be a single memory leak, or it can be the start of a leak cycle. A leak cycle occurs when you lose a reference to a group of objects. A memory leak leaks one object while a leak cycle leaks a group of objects.
Your code may not have any leak cycles, which would explain why your Cycles and Roots section shows less information than the tutorial. Choosing Call Tree instead of Cycles and Roots from the jump bar can help you find the areas of your code that are leaking memory.
I'm sure it's a memory leak, and I'm certainly no expert in memory allocation, but instruments says I have a memory leak of zero bytes...
Can someone explain what exactly that means?
The leaked object (including the responsible party that initiates it) should appear in the lower half part of the Instruments window, once you have selected the "Leaks Discovered".
Doing an "Analyze" (Command + I) in XCode will also show possible memory leaks in your code.
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:
64bit x86_64 no-GC: minimal memory is
freed. like 10%
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:
NSData *bla = [[NSData alloc] initWithContentsOfFile:#"/bigpoop"];
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.
A garbage collector doesn't necessarily release memory immediately.
In the case of the Objective-C garbage collector, you can send Cocoa's garbage collector object a collectIfNeeded message to suggest that now may be a good time to do some collection, or collectExhaustively to order it to immediately start collecting any and all garbage (but even this is interruptible). See the docs.
I have a very similar problem in iPhoneOS 3.2 and I really don't think that the memory is being reclaimed -- I eventually trigger memory warnings. There is a small chance that I have overlooked a mistake of my own but I have been very thorough.
I use NSKeyedUnarchiver's unarchiveObjectWithFile: to load a custom object that contains a single large NSData and another much smaller object. The dealloc method in my custom object gets called, the NSData object is released, its retainCount == 1 just before. Physical memory does not decrement by any amount, let alone a fraction of the NSData size, and with repetition memory warnings are reliably generated: I have test until I actually received level 2 warnings. =(
Before release:
(gdb) p (int) [(NSData *) pastItsWelcomeData retainCount]
$1 = 1
After release:
(gdb) p (int) [(NSData *) pastItsWelcomeData retainCount]
Target does not respond to this message selector.