【问题标题】:autoreleased objects freed, still running out of memory?自动释放的对象被释放,仍然内存不足?
【发布时间】:2012-07-02 01:30:02
【问题描述】:

http://i46.tinypic.com/2nquzag.png

我有一个应用程序在很长的计算过程中创建和释放数百万个 NSNumber。我将@autoreleasepools 与创建和处理这些对象的各种循环一起使用。正如您从仪器屏幕截图中看到的那样,我只有 10656 个活动 CFNumber,但此时应用程序由于无法为更多对象分配内存而崩溃。有超过 5700 万个 CFNumber 处于“临时”状态。这是否意味着他们被释放了?为什么 malloc 还不使用这些内存位置?

我已经包含了我正在使用的代码的模型。运行 testProbabilities 方法,它将每 10000 次迭代计数一次。在分配错误之前,我使用此代码进行了多达 790000 次迭代。然而 Instruments 对我来说看起来不错。

@implementation MMTest

NSMutableSet *predictedSet;


-(NSMutableArray *)calculateCDF {

//// NORMALIZE PROBABILITIES ////

float probabilitySum = 0;
float runningCDFTotal = 0;
float normaledProbability = 0;

@autoreleasepool {

    NSMutableArray *CDF = [[NSMutableArray alloc] init];

    probabilitySum = 4.5; ////This is just a placholder for additive iteration of an array which creates this sum.

    //// CREATE CDF ////

    int x;

    for (x=0; x<50; x++) {

        normaledProbability = .2/probabilitySum;////This is placeholder for calculation on same array as above.
        runningCDFTotal += normaledProbability;

        [CDF addObject:[NSNumber numberWithFloat:runningCDFTotal]];

    }

    return CDF;

}

}

-(NSMutableSet *)generateNumbers {

@autoreleasepool {

    int x;
    double r = 0;

    if (!predictedSet) {
        predictedSet = [[NSMutableSet alloc] init];
    }

    else {
        [predictedSet removeAllObjects];
    }

    for (x=0; x<5;) {

        r = arc4random_uniform(1000)/1000;////I'm actually using SFMT here instead.
        int num = 0;
        __weak id CDF = [self calculateCDF];


        if (r <= [[CDF objectAtIndex:[CDF count]-1] floatValue]) {

            for (NSNumber *cdf in CDF) {

                if (r <= [cdf floatValue]) {

                    num = [CDF indexOfObject:cdf]+1;

                    [predictedSet addObject:[NSNumber numberWithInt:num]];

                    x++;

                    break;


                }

            }

        }

    }

    return predictedSet;

}

}

-(void)testProbability {

int x = 0;
BOOL nextSetFound = NO;
NSSet *nextSet = [[NSSet alloc] initWithObjects:
                  [NSNumber numberWithInt:1],
                  [NSNumber numberWithInt:2],
                  [NSNumber numberWithInt:3],
                  [NSNumber numberWithInt:4],
                  [NSNumber numberWithInt:5],
                  nil];

while (nextSetFound == NO) {

    @autoreleasepool {

        __weak id newSet = [self generateNumbers];

        x++;

        if ([nextSet isSubsetOfSet:newSet]) {

            nextSetFound = YES;
            NSLog(@"%@", newSet);

        }   

        if (fmod (x,10000) == 0) {
            NSLog(@"%i", x);

        }

    }

}

NSLog(@"%i", x);

}

@end

【问题讨论】:

  • #Transitory 只是#Overall 和#Living 之间的区别。这并不表示存在问题。
  • 好的,但是完全解除分配的对象和暂时的对象有什么区别?
  • 没有区别。 #Transitory 列中计数的分配已完全解除分配。 “暂时”不是分配状态。它只是已被释放的对象的数量。它告诉一些关于内存管理器的负载,但没有关于当前或过去的内存消耗。
  • 这里有 1/1000000 的机会 NSNumber 是比 c 的内置类型更好的解决方案...
  • 感谢 Codo 的澄清。 @Justin:我需要将它们添加到数组并将内容与其他数组进行比较。所以 NSNumbers 使用这种方式更容易。

标签: objective-c memory-management automatic-ref-counting instruments


【解决方案1】:

不知道为什么,但上面这段代码现在可以正常工作了。在测试各种修改的过程中,我在 Xcode 中启用和禁用了 Guard Malloc 和 Malloc Stack Logging。突然间,我的修改版本开始运行,没有崩溃。然后我再次尝试了这个原始版本,现在它运行良好而没有崩溃。这没有任何意义,但我很高兴它起作用了。

@sergio:我想支持您的回答,至少是因为我提供了很多帮助和建议来进一步优化此代码,但我没有足够高的声誉。谢谢您的帮助。我很感激。

【讨论】:

    【解决方案2】:

    我错了还是你没有在任何地方发布CDF?这样,您每次调用 calculateCDF 时都会泄漏 CDF

    试试这个:

    id CDF = [self calculateCDF];
    

    而不是

     __weak id CDF = [self calculateCDF];
    

    id newSet = [self generateNumbers];
    

    而不是

    __weak id newSet = [self generateNumbers];
    

    指定weak 将使对象不属于newSet,因此不会被ARC 自动释放。

    另一方面,苹果explicitly adives against using __weak for stack variables

    在堆栈上使用 __weak 变量时要小心。考虑以下示例:

           NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
           NSLog(@"string: %@", string);
    

    虽然在初始赋值后使用了string,但在赋值时并没有其他对string对象的强引用;因此,它立即被解除分配。日志语句显示该字符串具有空值。 (例如,如果使用NSString * __weak string = [[NSString alloc] initWithString:@"First Name"],您将看不到效果。在这种情况下,initWithString: 只是返回永远不会释放的字符串常量。)

    根据我对本段的理解,您要么立即解除分配,要么发生内存泄漏。

    我在 iOS 4.3 iPhone 上尝试了您的代码(删除了 __weak),它在仪器下显示了一个平坦的“物理空闲内存”。

    旧答案:

    autorelease 的棘手之处在于自动释放池会在不可预知的时间耗尽(通常是在应用程序执行运行循环时)。这与使用 release 的不同之处在于,它会在您处理完一个对象的那一刻和释放内存的那一刻之间增加一些延迟。

    如果你将这个小延迟(就像autorelease 系统的惯性)乘以自动释放对象的数量,这可能会导致内存使用高峰。如果 iOS 在其中一个峰值期间检查内存使用情况,并且您的应用无法对随之而来的内存警告做出正确反应,那么您的应用就会被终止。

    另一方面,如果您分配了一个释放池,并且它在运行周期的同一步骤中变得很大,这也可能导致内存使用过多。

    您提供的代码细节很少,所以我不能更准确;无论如何,如果可能的话,我会尝试明确的release 对象。这将摆脱上述两个问题。或者您可以尝试处理较小的自动释放池(并且您将解决第二种问题)...

    我还建议您在 Instruments 中添加内存监视器,这样您就会看到系统释放内存的速度。

    PS:现在,您所附图片中让我印象深刻的是分配图是绝对平坦的:没有分配,没有释放。我预计内存会上升和下降(根据我的经验,这是一个健康的 iOS 应用程序的标志)。

    【讨论】:

    • 我同意这张图根本无法解释内存问题。总消耗量为 7.5 MB,没有峰值,没有问题...
    • 我觉得我错过了一些信息:我怀疑你的应用确实有一个内存分配上升的阶段(当内存警告出现时下降);当不再分配内存时,它似乎可以幸免于难并崩溃。在任何情况下,7.5 MB 的已用内存(由分配工具报告)足以让您的应用程序被杀死,这完全取决于系统剩余多少内存......另请参阅我关于使用内存监视器工具的编辑...
    • 图片可能具有误导性。该图并未一直缩小,因此是的,初始分配在开始时会增加。但是应用程序在这个尚未完成的长计算过程中崩溃了。由于 3 个自动释放池一直在使用,Live 字节在整个计算过程中保持非常均匀。我将尝试使用内存监视器进行进一步诊断。
    • i47.tinypic.com/b6dh09.png i48.tinypic.com/296myiq.png 使用内存监视器的较短运行的两个新屏幕。看来 Instruments 正在迅速耗尽所有可用内存,并且我的应用程序在运行后分配相对不变。仍然没有解释为什么应用程序在 Instruments 未运行时由于分配错误而崩溃......
    • 您确定要发布您的 NSNumber 实例吗?如果你将它们存储在字典中,它们将不会被释放......你能展示你如何创建你的 NSNumber 以及你如何处理它们的内存吗?
    猜你喜欢
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多