I have started out with Objective-C recently and trying to understand the memory management. I came across a peculiar problem yesterday which was causing a memory leak of over 150MB! I traced it down to a piece of code that was creating NSArray literal of NSNumbers. I managed to solve the issue using NSMutableArray (using addObject), but I haven't been able to grasp the concept as to why one method works and the other doesn't. I would love for someone with a better understanding of the memory management to explain the concept behind it so that I can avoid such mistakes in the future.
To better illustrate my question, let me also provide the code snippets. So for instance, I have a function that creates a NSArray of few NSNumbers and returns it and it gets called many times:
-(NSArray *)getNSNumberArray
{
float num1 = 23.56;
float num2 = 75.34;
float num3 = 223.56;
NSArray *numArray = #[[NSNumber numberWithFloat:num1], [NSNumber numberWithFloat:num2], [NSNumber numberWithFloat:num3]];
return numArray;
}
-(void)causeMemoryLeak
{
for (int i = 0; i < 2000000; i++)
{
NSArray *recieverArray = [self getNSNumberArray];
}
}
This results in memory being occupied to be more than 200MB on an iphone6. However if I change my function to be:
-(NSArray *)getNSNumberArray
{
float num1 = 23.56;
float num2 = 75.34;
float num3 = 223.56;
NSMutableArray *numMutableArray = [[NSMutableArray alloc] init];
NSNumber *nsNumber1 = [NSNumber numberWithFloat:num1];
NSNumber *nsNumber2 = [NSNumber numberWithFloat:num2];
NSNumber *nsNumber3 = [NSNumber numberWithFloat:num3];
[numMutableArray addObject:nsNumber1];
[numMutableArray addObject:nsNumber2];
[numMutableArray addObject:nsNumber3];
return numMutableArray;
}
-(void)causeMemoryLeak
{
for (int i = 0; i < 2000000; i++)
{
NSArray *recieverArray = [self getNSNumberArray];
}
}
This does not cause any memory issues.
Maybe I am missing something very obvious, but still cant figure out the reason behind this. Would really appreciate the help on this. There could also be better ways of doing it, and such answers are welcome but basically I am looking for an explanation behind it.
Many thanks in advance!
Attaching links to screenshots showing the memory allocation on device (iphone 6) (I can not attach images here as of now, so have to provide links)
Approach 1 Memory Allocation (memory is retained and not freed up): https://drive.google.com/open?id=0B-a9WJSBuIL4bTR5RTVuaWpqYkE&authuser=0
Approach 2 Memory Allocation (memory is freed up and there is no surge in memory allocation as well): https://drive.google.com/open?id=0B-a9WJSBuIL4QzQzbGYyQzZDdW8&authuser=0
I tried your code because I really didn't found any reason for memory leak and there is no memory leak.
When you use the regular array, the device runs all the code, and releases the memory only at the end. This causing the heap to alloc 250 MB, and release them at the end.
If you use the mutable array, the device releases the previous array before creating the new one, so there is no point with to many allocation.
I guess that the different may be caused by run time complexity of the called function in the loop.
As you are saying, only one approach is causing surge in memory, it does not seem so. According to the documentation, NSMutableArray uses slightly more memory because it can change size, it can't store the contents inside the object and must store a pointer to out of line storage as well as the extra malloc node for the storage.
In both the approaches, memory is freed up instantly, there is no issue with me.
Update 1 :
I have noticed that if you use the array, for example if you log the array, the memory is freed up instantly in your first approach. It is happening as you do not use that array. It solves your problem.Try this code :
-(NSArray *)getNSNumberArray
{
float num1 = 23.56;
float num2 = 75.34;
float num3 = 223.56;
NSArray *numArray = #[[NSNumber numberWithFloat:num1], [NSNumber numberWithFloat:num2], [NSNumber numberWithFloat:num3]];
return numArray;
}
-(void)causeMemoryLeak
{
for (int i = 0; i < 2000000; i++)
{
NSArray *recieverArray = [self getNSNumberArray];
NSLog(#"%#", recieverArray);
}
}
First, you need to read up on "autorelease" and "autorelease pools". Objects often are kept inside an autorelease pool and only disappear when the autorelease pool disappears. So a loop iterating two million times without using an autorelease pool is a bad, bad idea. You don't actually have a memory leak, the objects will all be released some time after the method returns.
Most methods returning objects return autoreleased objects, but the result of [[xxx alloc] init] is not autoreleased, so there you have the difference. But also ARC is quite clever, and if a method returns an autoreleased object and the caller immediately retains the object, the object will not actually be added to the autorelease pool. That's what happened when the object was passed to NSLog; in order to do that, it had to be retained first. No autorelease pool. In your code, the object was never used, so that optimisation didn't happened.
Of course your code is really useless, so you were punished for writing useless code.
Related
I have a some permutation/combination code that is iterating through 20 objects taking 5 at a time. When the list meets some criteria I print the objects that make up that list. Needless to say the loop is rather large. I place all of the combinations in an NSMutableArray inside of the loop. Once the objects have been added and pass/fail the test, I remove all of the object from the array. (Psuedo code below).
-(void)CreateCombinations
{
NSMutableArray *Combinations = [[NSMutableArray alloc] init];
#autoreleasepool {
NSArray *objectsList = [[NSArray alloc] initWithObjects:
#“Lisa”,#“Kevin”,…nil];
} //autorelease pool
while(!Finished)
{
Combinations = [self getNextCombo: Combinations]
if (goodCombination)
[self printCombos:Combinations]
[Combinations removeAllObject];
}
}
While monitoring the debug session, CPU and memory are at capacity. I am sure the looping is coming into play. I don't believe that I am reallocating the 'Combinations' Array for every iteration. If I am, is there something that I can do to make sure that it is properly deallocated or released before the next iteration of the loop?
When I add the #autorelease (which is before the loop) I get a "use of undeclared identifier error".
The code you've shown is completely fake so it's impossible to guess what your real code is doing wrong. But if you are using memory because you are piling up autoreleased objects during a loop, wrap the interior of the loop in an #autoreleasepool block:
while (...) {
#autoreleasepool {
// do stuff
}
}
The idea is to release the temporarily used memory every time through the loop.
This is my test code:
for (int i = 0; i < 1000000000; ++i) {
NSString *string = #"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:#"xyz"];
}
In ARC environment,the loop will not make the memory explode.In my case,it just cost 1.2MB RAM to run this loop.
But in MRC,the loop will make the memory explode unless use a #autoreleasepool code block.What makes me confused is there are many articles say that it is needed to put the codes in a #autoreleasepool when the codes in a for loop. but in this case,it doesn't matter without the #autoreleasepool.Please help me with this.thx.
update:
if I write the code like this:
for (int i = 0; i < 1000000000; ++i) {
NSString *string = [NSString stringWithFormat:#"aaaaaaaaaaaaaaaaaaa"];
}
the code will make the memory explode both in ARC and MRC. why?
stringWithFormat:
also return a autorelease object. I am puzzled by this...
Code compiled with ARC has to interoperate with code that wasn't compiled with ARC. Furthermore, you can't assume that Foundation and, in particular, the methods of NSString that you're calling, were compiled with ARC.
Those NSString methods have to be compiled in such a way that they can be called from both ARC and non-ARC code. That means that they must autorelease the objects they return, whether or not they themselves were compiled with ARC.
However, if those NSString methods are compiled with ARC, then they may use objc_autoreleaseReturnValue() to do the autoreleasing. If the caller was also compiled with ARC and it retains the object (because, for example, it is assigned to a strong local variable), then it will likely use objc_retainAutoreleasedReturnValue() on it. In that case, the use of the autorelease pool can be avoided. objc_autoreleaseReturnValue() can detect that the caller will use objc_retainAutoreleasedReturnValue() on the returned value, by examining the stack and the caller's instructions, and it won't autorelease the value and it will communicate that fact through a side channel to objc_retainAutoreleasedReturnValue() so that it doesn't retain the value.
So, in certain specific circumstances that you can't rely on determining yourself, code that would "normally" autorelease and then retain an object won't. It will simply transfer ownership.
ARC does not automatically drain the autorelease pool or introduce inner autorelease pools.
Because of the uncertainties, you should always use #autoreleasepool around any code that has a chance of spiking memory due to autoreleased objects. For example:
for (int i = 0; i < 1000000000; ++i) #autoreleasepool {
NSString *string = #"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:#"xyz"];
}
Yes, with ARC memory management is automated. But it is not always doing it the right way. Especially in a loop where many temp objects are created.
Temporary objects are objects that are created and used in the current iteration only. Like small strings that are appended to a bigger string.
These temp objects are freed after the loop completed, which might be too late. Thats why you have to put the body of your loop in an #autoreleasepool, to ensure the objects are freed immediately after each iteration of your loop.
for (int i = 0; i < 1000000000; ++i) {
#autoreleasepool {
NSString *string = [NSString stringWithFormat:#"aaaaaaaaaaaaaaaaaaa"];
}
}
Further reading: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
With ARC, the compiler is getting smarter.
Before ARC, methods like lowercaseString would create a retained/autoreleased object that is put into an autorelease pool. With ARC, the compiler is smart: lowercaseString checks that the method calling it immediately retains the result. In that case you would have a sequence retain / autorelease / retain, and in that case lowercaseString won't do it's retain / autorelease at all, so only the retain by the caller will be performed. That's faster (it saves a retain, and an autorelease, and the final release when the autorelease pool is closed), and the autorelease pool doesn't grow excessively.
That said, code that only works because the compiler performs an optimisation, is broken.
My original project was leaking so I searched for the leak. When I found it I created a simple new project.
The project uses ARC and the only code I added is the following.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
int elements = 10000000;
//memory usage 5,2 MB
NSMutableArray *array = [NSMutableArray arrayWithCapacity:elements];
//memory usage 81,7 MB
for (int i = 0; i < elements; i++) {
[array addObject:[NSObject new]];
}
//memory usage 234,3 MB
[array removeAllObjects];
//memory usage 234,3 MB
array = nil;
//memory usage 159,5 MB
}
After calling [array removeAllObjects] all NSObjects in the array should be deallocated and the memory usage should be 81,7 MB again.
What am I doing wrong?
Here
NSMutableArray *array = [NSMutableArray arrayWithCapacity:elements];
you are creating autoreleased object (autorelease pool).
Many programs create temporary objects that are autoreleased. These
objects add to the program’s memory footprint until the end of the
block. In many situations, allowing temporary objects to accumulate
until the end of the current event-loop iteration does not result in
excessive overhead; in some situations, however, you may create a
large number of temporary objects that add substantially to memory
footprint and that you want to dispose of more quickly. In these
latter cases, you can create your own autorelease pool block. At the
end of the block, the temporary objects are released, which typically
results in their deallocation thereby reducing the program’s memory
footprint
Wrap with #autoreleasepool {} method [NSMutableArray arrayWithCapacity:elements]:
NSMutableArray *array;
#autoreleasepool {
array = [NSMutableArray arrayWithCapacity:elements];
// [NSMutableArray arrayWithCapacity:] creates object with retainCount == 1
// and pushes it to autorelease pool
// array = some_object; usually (and in this case also) is transformed by ARC to
// [array release]; [some_object retain]; array = some_object;
// so here array will have retainCount == 2 and 1 reference in autorelease pool
} // here autorelease pool will call `release` for its objects.
// here array will have retainCount == 1
or change it to
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:elements];
You've been bitten by the dreaded autorelease pool. Essentially to make MRC (manual reference counting) manageable by people instead of releasing an object immediately it can be handed to the autorelease pool (an instance of NSAutoreleasePool, it's documentation gives more details) which will retain the object until the pool is later drained. ARC (automatic reference counting) could be designed so the autorelease machinery is not needed, but to maintain compatibility with MRC is remains.
The pool automatically drained at the end of a run loop cycle - i.e. when the application has finished processing an event. However if an application creates a lot of temporary objects and then discards them is some localised part of the program then a using a local autorelease pool can drastically reduce the maximum memory use. It is not that such temporary objects will not be release, just that they will live far longer than needed. A local pool can be create with the #autoreleasepool { ... } construct.
You can see the effect in your example by wrapping the whole of the body of applicationDidFinishLaunching:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
#autoreleasepool
{
...
}
}
and stepping through with the debugger.
In your real code you need to work back from the point which is producing lots of temporary objects to locate a suitable point to add an autorelease pool.
HTH.
Addendum
It is not the objects in your array that are not getting released when you think they should , you can test this by using a simple class which counts initialisations and deallocations, e.g.:
#interface TestObject : NSObject
+ (void) showCounts;
#end
#implementation TestObject
static uint64_t initCount = 0, deallocCount = 0;
- (id) init
{
self = [super init];
if(self) initCount++;
return self;
}
- (void) dealloc
{
deallocCount++;
}
+ (void) showCounts
{
NSLog(#"init: %llu | dealloc: %llu", initCount, deallocCount);
initCount = deallocCount = 0;
}
#end
Use this instead of NSObject and call showCounts after you are done with your test - try with/without autorelease, etc.
Your memory is always getting released, it is just the time at which it is release that is the issue. Some objects end up in an autorelease pool, either the default one which is emptied once per event, or a local one.
Unless you create a lot of temporary objects in response to a single event you normally won't see an issue. Consider whether you are chasing a real issue for your application here. If you are among the things to try to alleviate the problem are:
Avoid uses of convenience constructors of the form <name>WithX... which are shorthands for [[[C alloc] initWithX...] autorelease]. On many, but not all, occasions the compiler can remove such objects from the autorelease pool just after the convenience constructor returns (and your case appears to be one in which is can fail). The better route is to use alloc/init, new (shorthand for alloc/init) or, if provided, newWithX... (shorthand for alloc/initWithX...). Try these options with your example and see the differences in when (not if) the memory is released.
Well placed #autoreleasepool blocks.
HTH
I have a loop where I'd like to redefine the contents of an array with every iteration. I'm worried about leaks. Is there anything wrong with this code?
for (int i=0;i<numberOfReps;i++){
NSArray *shuffledArray=[self shuffleArray:originalArray];
// use shuffled array
}
Thanks for reading!
EDIT:
Here is shuffleArray (credit to Kristopher Johnson from What's the Best Way to Shuffle an NSMutableArray?):
-(NSArray*)shuffleArray:(NSArray*)array{
NSMutableArray *newArray=[NSMutableArray arrayWithArray:array];
NSUInteger count = [newArray count];
for (NSUInteger i = 0; i < count; ++i) {
// Select a random element between i and end of array to swap with.
int nElements = count - i;
int n = (arc4random() % nElements) + i;
[newArray exchangeObjectAtIndex:i withObjectAtIndex:n];
}
return [NSArray arrayWithArray:newArray];
}
(And I am using ARC.)
Assuming we're using ARC and the shuffleArray method is fine, this bit of code will be fine too. There's no leak in the code you've posted.
Each shuffledArray is deallocated at the end of each iteration of the loop (unless you're saving it somewhere else). And there is only one originalArray.
EDIT: After the edit, again, assuming we're using ARC, there's no leak with the code. newArray is released as soon as shuffleArray returns. shuffledArray is released at the end of the loop iteration. originalArray has reference outside the for loop, and remains in memory until it has no more references.
The only possible leak here would be whatever you do with shuffledArray in the //use shuffled array section of the loop.
That depends.
Only if you're sure that the method shuffleArray returns an autoreleased object then there's no harm with your code, (and this assuming you're not using ARC).
EDIT:
After seeing your code update, regardless if you're using ARC, there's no leak
I'm trying to get my mind around one aspect of memory management in the iPhone SDK.
If I ran:
for (int x = 0; x < 10; x++) {
NSMutableArray *myArray = [[NSMutableArray alloc] init];
}
Am I creating 10 myArray objects in memory, or does each alloc overwrite the previous? If the latter, I presume I would only need one [myArray release] after the loop to clean up. If the former, I presume I need the release inside the For loop.
Thanks.
You get ten different allocations and you need to release them if you do not want memory leaks.
for (int x = 0; x < 10; x++) {
NSMutableArray *myArray = [[NSMutableArray alloc] init];
....
[myArray release];
}
If you don't release, the leaked object would actually 10, not 9 as per comment, since outside of the loop you don't have access to the loop local variable and the last allocated object would also be unreachable.
Actually, you have 10 objects with 10 leaks. Once you leave the loop, myArray is no longer in scope (and is therefore inaccessible), so there is no way to release the 10th allocation either.
You're creating 10 objects, 9 of which are leaked.
You should release after you use them in the end of the loop.
This also not only about iPhone SDK. It's basic Cocoa memory management. Also works on the Mac.
In that case you are creating 10 different Array objects each one with a retain count of 1, and no reference whatsoever. This would be a "memory leak factory" with the 10 objects never beign released from the memory. :)
oooops... did not see the release...9 leaked arrays.
In addition to what everyone (rightly) said, Cocoa also supports autoreleased objects. If you rephrase your snippet thus:
for (int x = 0; x < 10; x++)
{
NSMutableArray *myArray = [NSMutableArray arrayWithObjects: ...];
//....
}
you still allocate 10 different arrays, but none are leaked. They are autoreleased eventually.
for (int x = 0; x < 10; x++) {
NSMutableArray *myArray = [NSMutableArray array]; //Its an autorelease
....
}
This creates 10 different NSMutableArray objects. You actually do not need to release them explictly.myArray is autoreleased at the end of the run loop.
You take ownership of an object if you create it using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy), or if you send it a retain message. You are responsible for relinquishing ownership of objects you own using release or autorelease. Any other time you receive an object, you must not release it.
In NSMutableArray *myArray = [NSMutableArray array];, you do not take ownership of the array, and it will be passed to you autoreleased.
You can learn more about memory management here.