【问题标题】:Leaking memory with Cocoa garbage collectionCocoa 垃圾回收导致内存泄漏
【发布时间】:2010-01-19 17:00:10
【问题描述】:

我一直在拼命想弄清楚我是如何在垃圾收集的 Cocoa 应用程序中发生内存泄漏的。 (Activity Monitor 中的内存使用量会不断增长,使用 GC Monitor 工具运行应用程序也会显示不断增长的图表。)

我最终将其缩小到代码中的单一模式。数据被加载到 NSData 中,然后由 C 库解析(数据的字节和长度被传递给它)。 C 库有回调,它会触发并返回子字符串的起始指针和长度(以避免内部复制)。但是,出于我的目的,我需要将它们变成 NSStrings 并保留一段时间。我通过使用 NSString 的 initWithBytes:length:encoding: 方法来做到这一点。我以为这会复制字节并且 NSString 会适当地管理它,但是出了点问题,因为这会像疯了一样泄漏。

此代码会“泄漏”或以某种方式欺骗垃圾收集器:

- (void)meh
{
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"holmes" ofType:@"txt"]];
    const int substrLength = 80;

    for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) {
        NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding];
        [cocoaString length];
    }
}

我可以将其放入计时器中,然后通过 Activity Monitor 和 GC Monitor 工具观察内存使用量的上升和上升。 (holmes.txt 为 594KB)

这不是世界上最好的代码,但它说明了问题所在。 (我正在运行 10.6,该项目的目标是 10.5 - 如果重要的话)。我阅读了垃圾收集文档并注意到了许多可能的陷阱,但我认为我没有做任何明显违反这里规则的事情。不过问也无妨。谢谢!

Project zip

这是一张不断增长的对象图的图片:

【问题讨论】:

  • 我在运行它时看不到任何泄漏,运行几分钟后它的内存使用量在 7.5MB 和 9MB 之间徘徊。
  • 这很奇怪。我在 10.6.2。我内置了调试和发布。在所有情况下,我都看到了不断增长的记忆。 wtf...
  • 我现在让其他人在 10.5 上运行它,他报告说它似乎也没有增长。我需要让其他 10.6+ 的人尝试一下。
  • 使用“泄漏”仪器运行我没有泄漏,使用“对象分配”给出了 Bryan 观察到的行为
  • Leaks 在雪豹上的 GC 下运行良好,顺便说一句。但是,它往往不会显示垃圾但尚未收集的东西。这纯粹是一个与阈值相关的问题。

标签: cocoa garbage-collection


【解决方案1】:

这是一个不幸的边缘案例。请提交错误 (http://bugreport.apple.com/) 并附上您出色的最小示例。

问题有两个;

  • 主事件循环未运行,因此收集器未通过 MEL 活动触发。这使得收集器只执行其正常背景的基于阈值的收集。

  • 数据将从文件读取的数据存储到从 malloc 区域分配的 malloc 缓冲区中。因此,GC 占分配 - NSData 对象本身 - 非常小,但指向非常大的东西(malloc 分配)。最终结果是没有达到收集器的阈值,也没有收集。显然,需要改进这种行为,但这是一个难题。

这是一个非常容易在微基准测试或隔离中重现的错误。在实践中,通常有足够多的事情不会发生这个问题。但是,在某些情况下它确实会出现问题。

将您的代码更改为此,收集器将收集数据对象。请注意,您不应该经常使用collectExhaustively——它确实会占用 CPU。

- (void)meh
{
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"holmes" ofType:@"txt"]];
    const int substrLength = 80;

    for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) {
        NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding];
        [cocoaString length];
    }
    [data self];
    [[NSGarbageCollector defaultCollector] collectExhaustively];
}

[data self] 在最后一次引用后保持数据对象处于活动状态。

【讨论】:

  • 非常感谢您的解释和可能的解决方法。归档雷达://556417。
  • 谢谢——radar://7556417,顺便说一句。
猜你喜欢
  • 2012-05-21
  • 2018-05-10
  • 2012-06-28
  • 1970-01-01
  • 1970-01-01
  • 2012-01-03
  • 1970-01-01
  • 2011-02-12
  • 2016-11-02
相关资源
最近更新 更多