13

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

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

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

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

这种行为有解释吗?


更新:

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

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

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

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

上图 32bit 下 64 http://kttns.org/zguxn

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


更新 2:

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

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

操作系统是 10.6.0

4

3 回答 3

10

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

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

然而,这个声明:

64bit x86_64 no-GC:释放最少的内存。像 10%

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

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

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

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

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

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

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

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

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

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

于 2009-09-05T18:56:18.580 回答
2

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

对于 Objective-C 垃圾收集器,您可以向 Cocoa 的垃圾收集器对象发送一条collectIfNeeded消息,建议现在可能是进行一些收集的好时机,或者collectExhaustively命令它立即开始收集所有垃圾(但即使这样是可中断的)。请参阅文档

于 2009-09-05T16:10:28.673 回答
0

我在 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]
目标不响应此消息选择器。

于 2010-04-29T09:09:19.937 回答