在Mac OS X的64位进程中,一个适度大小的内存分配如何失败?

12
我正在构建一个照片书布局应用程序。该应用程序经常将JPEG图像解压缩为内存位图缓冲区。这些图像的大小被限制在100兆像素(通常不超过15兆像素)。
有时候,这些缓冲区的内存分配会失败:[[NSMutableData alloc] initWithLength:]返回nil。这似乎发生在系统空闲物理内存接近零的情况下。
我对Mac OS X中的虚拟内存系统的理解是,在64位进程中进行的分配从理论上来说是不会失败的。有16艾字节的地址空间,我每次尝试最多分配400兆字节。理论上,我可以分配400亿个这样的缓冲区,而不会达到可用地址空间的硬限制。当然,实际限制会阻止这种情况的发生,因为交换空间受引导卷大小的限制。实际上,我只做了很少量的这些分配(不到十个)。
我不理解的是,即使在物理内存非常低的情况下,分配仍然会失败。我认为,只要还有交换空间,内存分配就不会失败(因为这些页面甚至没有映射到此时点)。
该应用程序是垃圾收集的。
编辑:
我有时间进一步研究了这个问题,以下是我的发现:
  1. 这个问题只会出现在垃圾回收的进程中。
  2. 当从NSMutableData分配失败时,使用普通的malloc仍然可以成功分配相同数量的内存。
  3. 当整体物理内存接近零(即将开始交换)时,错误总是发生。

我猜NSData在垃圾回收下运行时使用NSAllocateCollectable来执行分配,而不是malloc

我的结论是,当物理内存较低时,收集器无法分配大块内存。我对此还是不太理解。


你的操作系统是64位的吗?一个进程可以运行在64位模式下,但我认为只有当操作系统也以64位模式运行时,才能获得完整的64位优势。我认为这可能会对你的情况产生影响。 - Mark
6个回答

9
答案在于libauto的实现。
在64位平台上,从OS X 10.6开始为垃圾收集内存分配了8Gb的区域。这个区域被分成两半,一半用于大型(>=128k)的内存分配,另一半用于小型(<2048b)或中型(<128k)的内存分配。
因此,在10.6上,您有4GB的内存可用于垃圾收集内存的大型分配。在10.5上,该区域的大小为32Gb,但是苹果在10.6上将其降低到了8Gb。

1

另一个猜测是,你的同事的机器配置了更严格的每个用户进程最大内存设置。要检查,请在控制台中键入

ulimit -a

对我来说,我得到:

~ iainmcgin$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 256
pipe size            (512 bytes, -p) 1
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 266
virtual memory          (kbytes, -v) unlimited

从我的上述设置中,似乎没有针对内存使用的每个进程限制。由于某种原因,这可能不适用于您的同事。

我正在使用Snow Leopard:

~ iainmcgin$ uname -rs
Darwin 10.6.0

好主意,谢谢,但它似乎不是所描述的问题的源头。我已经能够在几台机器上重现这个错误,包括我的机器,并且没有自定义vmem限制。 - Nikolai Ruhe

0

尽管64位计算机理论上可以寻址18 EB,但当前处理器仅限于256 TB。当然,您也不会达到这个限制。但是,您的进程一次可以使用的内存量受可用RAM的限制。操作系统还可能限制您可以使用的RAM数量。根据您发布的链接,“即使对于可用4 GB或更多RAM的计算机,系统也很少将这么多RAM分配给单个进程。”


感谢指出受限地址空间的问题。通过一个小型命令行测试,显示 Mac OS X 中的 64 位进程最多可以分配 128 TB 的内存。但正如您所说,我的进程没有达到这个限制。关于您回答中提到的其他部分:分配并不会 使用任何内存,而只是保留页面。只有在 映射 这些页面(通过读取或写入它们)之后,才会消耗物理内存。因此,在 Mac OS 上本身的分配不应该失败(与 iOS 不同,Mac OS 可以将内存交换出去)。 - Nikolai Ruhe
@Nikolai 如果操作系统限制应用程序一次使用的页面数量,则分配可能失败,因此,如果您在其他内存有机会换出之前尝试分配大量内存,则操作系统可能决定不让您这样做。 - ughoavgfhw
你知道操作系统根据可用物理页面来决定分配失败吗?你有这方面的资料吗? - Nikolai Ruhe
我没有看到任何说明它会这样做,但这是可能的。如果在任何东西被分页之前尝试使用比RAM更多的内存,那么就没有地方可以放置它。 - ughoavgfhw
我会认为在这种情况下分配将会被阻塞。或者当需要交换时,页面的映射(访问时)可能会变得更慢(这似乎更有可能)。但这一切都只是猜测。 - Nikolai Ruhe
如果您能更详细地解释一下这个限制就太好了。请参阅我的相关问题:https://stackoverflow.com/questions/47595127/what-are-the-limitations-with-malloc-on-a-64-bit-process-on-macos - Thomas Tempelmann

0

你可能已经用尽了交换空间。即使你有一个交换文件和虚拟内存,可用的交换空间仍然受限于硬盘上交换文件的空间。


不错的观点,但是在我的机器上发生错误的时候,启动卷还剩下120GB的可用空间。 - Nikolai Ruhe

0

这可能是内存碎片问题。也许在分配时没有任何单个连续的400 MB块可用?

您可以尝试在应用程序生命周期的最开始分配这些大块,以防止堆被众多较小的分配所破坏。


分段是个好主意,但在这种情况下问题的根源似乎在别处(因为malloc仍然有效,请参见我的答案)。 - Nikolai Ruhe

-1

initWithBytes:length: 尝试在活动内存中分配全部长度,基本相当于 malloc() 的大小。如果长度超过可用内存,则返回 nil。如果您想使用大文件与 NSData,我建议使用 initWithContentsOfMappedFile: 或类似的初始化器,因为它们使用 VM 系统在需要时将文件的部分拉入和推出活动内存。


正如我在问题中所解释的那样,我正在使用缓冲区来存储解压缩图像,因此映射文件不是一个选项。关于您的声明:“如果长度超过可用内存,您将获得nil。”:这正是问题所在。我的理解是,虚拟内存(与交换空间结合使用)只要地址空间没有填满,就不应该出现任何分配失败的情况。 - Nikolai Ruhe
1
非常正确,对此我感到抱歉。在我的(有限的)测试中,NSData 在 64 位进程中分配许多千兆字节的内存从未出现问题。它在 32 位机器上有 2GB 的限制。无论如何,因为 NSData 是一个对象,它必须存在于 Objective-C 运行时中,该运行时在堆上分配所有对象。对于非常大的分配,您可能希望使用 CFData 函数,因为它们不会产生这种开销,并允许您使用 Mach 的写时复制内存管理。您还可以检查系统日志,以查看分配失败是否记录了异常,就像它应该做的那样。 - Jon Shier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接