Java mmap在Android上失败,“mmap失败:ENOMEM(内存不足)”

时间:2016-07-10 16:11:24

标签: java android memory-management android-ndk mmap

使用Java在Android上映射大型文件的内存效果很好。但是,即使有多个映射调用,当映射总量超过~1.5GB时,它也会失败:

ByteBuffer buf = raFile.getChannel().map(allowWrites ? FileChannel.MapMode.READ_WRITE : FileChannel.MapMode.READ_ONLY, offset, byteCount);

请参阅完整讨论here。注意:它在服务器Linux上不会失败。为应用程序启用了android:largeHeap =“true”。

以下Java代码被称为几百次,每次调用请求~1MB:

ulimit

避免请求一个通常难以找到的大型连续内存块。请参阅完整代码here。请记住,将“段大小”(即单个地图调用的大小)加倍无效,这意味着它会停在类似的内存位置。另外值得注意的是,两个略低于限制的应用程序正在执行正常(暗示每个进程限制)。

相关问题包括hereherehereherehereherehere和{{ 3}}

使用多个文件而不是一个具有多个映射的文件会有帮助吗?

我已经读过,这可能是虚拟地址空间的每个进程限制 。哪里可以找到更多相关信息?我可以使用NDK更改此设置,例如如何致电{{1}}? here可以帮我一点吗?

更新

有关可在Java中使用的mmap工具,请参阅我的回答madvise

3 个答案:

答案 0 :(得分:6)

您的问题肯定是由虚拟地址空间耗尽造成的。可能你的问题在32位Android设备上重现,用户地址空间可用,物理上限制为2GB,无法碰撞。 (虽然它可能是3GB(不太可能),并且它是在OS构建过程中配置的)。可能~500 MB用于系统库,JVM及其堆。并且~1.5 GB可供您使用。

在这种情况下IMO的唯一方法 - 继续仅映射现在真正使用的文件部分,并尽快取消映射未使用的部分。您可以使用某种滑动窗口,其中只有一小部分文件将被映射到内存,当您完成时 - 取消映射该部分,推进您的窗口位置并映射该更新的窗口,依此类推。

此外,当您映射整个大文件时 - 您的进程成为系统内存杀手的有吸引力的受害者。因为当你读取这样的映射文件时 - 物理内存的消耗会增加,并且在某些时刻进程将被终止。

答案 1 :(得分:1)

由于我们无法通过API(不需要root访问权限)增加Android上的虚拟地址限制,我还没有在Android源代码中看到这一点。我看到的唯一可能的解决方案是实现一种缓存,如果已经映射了一定数量的段,则会在访问时对片段进行mmaps并释放旧片段。这意味着我们正在自动执行操作系统通常为我们所做的工作,这有点难看。

要在Android下运行,可以使用this answer / util-mmap。希望有人可以在某些时候为Android实现这样的mmap缓存,甚至可能是我们:)

答案 2 :(得分:0)

尝试在清单中添加largeHeap。可能有用