在内存有限的系统上写入大文件时,如何避免mapFailed()错误

Pau*_*lor 8 java mappedbytebuffer

我刚刚在opensrc库代码中遇到一个错误,该代码分配了一个大缓冲区来修改大型flac文件,该错误只发生在使用Java 1.8.0_74 25.74-b02 32bit的具有3Gb内存的旧PC机上

最初我曾经只是分配一个缓冲区

ByteBuffer audioData = ByteBuffer.allocateDirect((int)(fc.size() - fc.position()));
Run Code Online (Sandbox Code Playgroud)

但有一段时间我有它

MappedByteBuffer mappedFile = fc.map(MapMode.READ_WRITE, 0, totalTargetSize);
Run Code Online (Sandbox Code Playgroud)

我(错误)的理解是映射缓冲区使用的内存少于直接缓冲区,因为整个映射缓冲区不必同时只在内存中使用的部分.但是这个答案说使用映射的字节缓冲区是一个坏主意,所以我不清楚它是如何工作的

Java Large File Upload抛出java.io.IOException:Map失败

完整的代码可以在这里看到

Tra*_*vis 2

尽管映射缓冲区在任何一个时间点可能使用较少的物理内存,但它仍然需要等于缓冲区总(逻辑)大小的可用(逻辑)地址空间。更糟糕的是,它可能(可能)要求地址空间是连续的。无论出于何种原因,该旧计算机似乎无法提供足够的额外逻辑地址空间。两种可能的解释是 (1) 有限的逻辑地址空间 + 大量的缓冲存储器要求,以及 (2) 操作系统对可映射为 I/O 文件的内存量施加的一些内部限制。

关于第一种可能性,请考虑这样一个事实:在虚拟内存系统中,每个进程都在自己的逻辑地址空间中执行(因此可以访问完整的 2^32 字节寻址)。因此,如果在您尝试实例化MappedByteBufferJVM 进程的当前大小加上总(逻辑)大小MappedByteBuffer大于 2^32 字节(约 4 GB),那么您将遇到一个OutOfMemoryError(或者该类选择抛出的任何错误/异常,例如IOException: Map failed)。

关于第二种可能性,评估这种情况的最简单方法可能是在您尝试实例化MappedByteBuffer. 如果 JVM 进程分配的内存 + 所需的内存totalTargetSize远低于 2^32 字节上限,但您仍然收到“映射失败”错误,那么很可能是某些内部操作系统对内存映射文件大小的限制根本原因。

那么,就可能的解决方案而言,这意味着什么呢?

  1. 只是不要使用那台旧电脑。 (更好,但可能不可行)
  2. 确保 JVM 中的其他所有内容在MappedByteBuffer. (看似合理,但可能无关紧要且绝对不切实际)
  3. 将该文件分成更小的块,然后一次仅对一个块进行操作。 (可能取决于文件的性质)
  4. 使用不同/较小的缓冲区。...并且只能忍受性能下降。 (这是最现实的解决方案,即使它是最令人沮丧的)

totalTargetSize另外,您的问题案例到底是什么?


编辑:

经过一番挖掘后,很明显 IOException 是由于32 位环境中地址空间不足造成的。即使文件本身小于 2^32 字节,由于缺乏足够的连续地址空间,或者由于 JVM 中其他足够大的地址空间需求,同时结合MappedByteBuffer请求,也可能发生这种情况(请参阅注释) 。需要明确的是,即使最初的原因是 ENOMEM ,仍然可以抛出 IOE 而不是 OOM 。此外,特别是较旧的 [在此处插入 Microsoft 操作系统] 32 位环境似乎存在问题(例如例如)。

所以看起来你有三个主要选择。

  1. 共使用“ 64位JRE或...另一个操作系统”。
  2. 使用不同类型的较小缓冲区并以块的形式对文件进行操作。(并且由于不使用映射缓冲区而导致性能下降)
  3. 出于性能原因继续使用MappedFileBuffer,但也以较小的块对文件进行操作,以解决地址空间限制。

我将使用较小的块作为第三个的原因是因为在取消映射(示例MappedFileBuffer)时存在已确定且未解决的问题,这是您在处理每个块之间必须执行的操作,以避免达到 32 位由于累积映射的组合地址空间占用而导致的上限。 (注意:这仅适用于 32 位地址空间上限而不是某些内部操作系统限制的问题...如果是后者,则忽略本段) 您可以尝试此策略(删除所有引用,然后运行GC),但是您基本上会受到 GC 和底层操作系统如何与内存映射文件交互的影响。尝试或多或少地直接操作底层内存映射文件的其他潜在解决方法(示例)是极其危险的,并且受到 Oracle 的特别谴责(请参阅最后一段)。最后,考虑到 GC 行为无论如何都是不可靠的,而且官方文档明确指出“内存映射文件的许多细节未指定”,我不建议这样使用,无论您可能读到任何解决方法。MappedFileBufferMappedFileBuffer

因此,除非您愿意承担风险,否则我建议要么遵循 Oracle 的明确建议(第 1 点),要么使用不同的缓冲区类型将文件作为一系列较小的块进行处理(第 2 点)。