【问题标题】:cocoa 64bit binaries leak memory? (releasing NSData does not free memory)可可64位二进制文​​件泄漏内存? (释放 NSData 不会释放内存)
【发布时间】:2010-11-25 21:36:22
【问题描述】:

我已经使用不同版本的应用程序玩了一段时间,似乎发生了奇怪的事情:

我的应用有 5mb 的空闲空间。上传文件时保留文件大小的内存。上传后保留的内存应该被释放。现在构建有所不同(gc = 垃圾收集器):

  • 32bit i386 no-GC:立即释放所有内存。
  • 32bit i386 GC:几乎所有内存都会立即释放。过一段时间后剩下的。
  • 64 位 x86_64 no-GC:释放的内存最少。像 10%
  • 64bit x86_64 GC:根本没有释放内存。内存会保留几个小时。 (活动星期一)

我正在使用带有 CLANG 的 LLVM。我一直在运行今天的仪器,并检查泄漏/僵尸/等。一切似乎都很干净。 (该应用程序相当简单。)

对这种行为有解释吗?


更新:

这是一些奇怪的东西。我把问题归结为:

我将一个 20mb 的文件加载到 NSData 中并释放它。我在没有启用任何垃圾收集的情况下这样做。代码是:

NSData *bla = [[NSData alloc] initWithContentsOfFile:@"/bigshit"];
[bla release];

当我为 i386 32bit 构建时,20mb 被分配和释放。当我将构建切换到 64 位 x86_64 时,版本什么也不做。分配的 20mb 停留时间。

upper pic 32bit lower 64 http://kttns.org/zguxn

除了上一个是为 32 位构建的,而下一个是为 64 位构建的之外,这两个应用程序之间没有区别。没有 GC 正在运行。 (启用 GC 也会出现同样的问题。)


更新 2:

当我只使用 applicationDidFinishLaunching: 中的上层代码从头开始创建一个新的可可应用程序时,可以观察到相同的行为。在 64 位模式下,内存不会被释放。 i386 按预期工作。

同样的问题出现在 NSString 而不是 NSData 上。当我启动 64 位内核时,它也会出现。 (启动时保持 64。)

操作系统是 10.6.0

【问题讨论】:

  • 您是否尝试过分配和释放大量 NSData 实例以查看它们中的任何一个的内存是否被回收?除非内存不足,否则程序可能不会将内存返回给操作系统。
  • 我只是试图在 10.6 上重现这个,但没有运气。您是否为您的测试应用提交了错误?
  • 您的应用程序运行正常并且没有泄漏内存。请参阅下面的答案以获得半详细的解释。

标签: objective-c cocoa memory-leaks 64-bit nsdata


【解决方案1】:

垃圾收集器不一定会立即释放内存。

在 Objective-C 垃圾收集器的情况下,您可以向 Cocoa 的垃圾收集器对象发送 collectIfNeeded 消息,建议现在可能是进行一些收集的好时机,或者 collectExhaustively 命令它立即启动收集所有垃圾(但即使这样也是可中断的)。见the docs

【讨论】:

    【解决方案2】:

    首先,使用Instrument的Object Graph instrument来验证内存不再被认为是在使用中;在某处没有保留计数或强引用。

    如果它不再被使用,那么内存只是因为你没有达到收集器关心的阈值而留下来。

    但是,这个声明:

    64bit x86_64 no-GC:最小内存是 释放。 10%

    让我很警惕。具体来说,如果您的代码设计为在非 GC 中工作——使用保留/释放——那么(a)你有内存泄漏,如果使用 CFRetain 或某种全局缓存,这可能会影响 GC 或(b ) 你没有使用正确的工具来确定你是否有内存泄漏。

    那么,你如何确定你正在泄漏内存?

    更新;您正在使用活动监视器来监视进程的 RSIZE/VSIZE。除了“我的流程是否会随着时间而增长”之外,这实际上不会告诉您任何有用的信息。

    很有可能(我没有查看源代码),这段代码:

    NSData *bla = [[NSData alloc] initWithContentsOfFile:@"/bigpoop"];
    

    将导致 20MB 文件mmap()'d 进入进程。根本不涉及 malloc() 样式分配。相反,操作系统将 20MB 的连续地址空间分配给您的进程,并将文件的内容映射到其中。当您阅读 NSData 的内容时,它会在文件中出现页面错误。

    当您释放bla 时,映射被破坏。但这并不意味着 VM 子系统会将应用程序的地址空间减少 20MB。

    因此,您正在烧毁一堆地址空间,而不是实际内存。由于您的进程是 64 位的,地址空间几乎是无限资源,使用地址的成本非常低,这就是操作系统以这种方式实现的原因。

    即没有泄漏,并且您的应用程序行为正确,GC 或否。

    这是一个常见的误解,因此,该问题已加注星标。

    【讨论】:

    • 我正在使用内存泄漏工具。作为 32 位构建,一切都按预期工作。
    • 我想跟进此事。也许我应该创建一个新问题,但我会先尝试这个。因此,假设我想通过网络发送一个巨大的文件,然后使用 NSDataReadingMappedAlways 将其加载到 NSData 中。我把它分成几部分,然后逐步发送。文件发送结束后,每个字节都被读取,无论文件有多大,我的 RSIZE 都会增长吗?可以吗?
    【解决方案3】:

    我在 iPhoneOS 3.2 中遇到了一个非常相似的问题,我真的不认为内存正在被回收——我最终触发了内存警告。我忽略了自己的错误的可能性很小,但我已经非常彻底。

    我使用 NSKeyedUnarchiver 的 unarchiveObjectWithFile: 来加载包含单个大 NSData 和另一个小得多的对象的自定义对象。我的自定义对象中的 dealloc 方法被调用, NSData 对象被释放,它的 retainCount == 1 之前。物理内存不会减少任何数量,更不用说 NSData 大小的一小部分了,并且可以可靠地生成重复内存警告:我进行了测试,直到我实际收到 2 级警告。 =(

    发布前:

    (gdb) p (int) [(NSData *) pastItsWelcomeData retainCount]
    $1 = 1

    发布后:

    (gdb) p (int) [(NSData *) pastItsWelcomeData retainCount]
    目标不响应此消息选择器。

    【讨论】:

    • 你有不同的问题。 iPhone 上不存在 GC,而且retainCount 无论如何都不能用于调试对象生命周期问题。 (请改用 Instruments。)由于这是对不同问题的描述,并且没有回答问题,因此您应该将其作为单独的问题发布。
    猜你喜欢
    • 2012-06-08
    • 2011-01-27
    • 2019-08-18
    • 2017-04-24
    • 2014-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多