尽管映射缓冲区可能在任何时间点使用较少的物理内存,但它仍然需要一个可用的(逻辑)地址空间,该地址空间等于缓冲区的总(逻辑)大小。更糟糕的是,它可能(可能)要求地址空间是连续的。无论出于何种原因,那台旧计算机似乎无法提供足够的额外逻辑地址空间。两种可能的解释是 (1) 有限的逻辑地址空间 + 大量的缓冲内存需求,以及 (2) 操作系统对可映射为 I/O 文件的内存量施加的一些内部限制。
关于第一种可能性,请考虑这样一个事实,即在虚拟内存系统中,每个进程都在其自己的逻辑地址空间中执行(因此可以访问完整的 2^32 字节的寻址)。因此,如果--在您尝试实例化的时间点MappedByteBuffer
--JVM 进程的当前大小加上总(逻辑)大小MappedByteBuffer
大于 2^32 字节(约 4 GB),那么您将遇到一个OutOfMemoryError
(或该类选择抛出的任何错误/异常,例如IOException: Map failed
)。
关于第二种可能性,可能最简单的评估方法是在您尝试实例化MappedByteBuffer
. 如果 JVM 进程分配的内存 + 所需totalTargetSize
的内存远低于 2^32 字节上限,但您仍然收到“映射失败”错误,则可能是内存映射文件大小的某些内部操作系统限制是根本原因。
那么,尽可能解决方案意味着什么?
- 只是不要使用那台旧电脑。 (最好,但可能不可行)
- 确保 JVM 中的所有其他内容在
MappedByteBuffer
. (似是而非,但可能不相关且绝对不切实际)
- 将该文件分成更小的块,然后一次只操作一个块。 (可能取决于文件的性质)
- 使用不同的/更小的缓冲区。...并忍受性能下降。 (这是最现实的解决方案,即使它是最令人沮丧的)
totalTargetSize
另外,您的问题案例到底是什么?
编辑:
在进行了一些挖掘之后,很明显 IOException 是由于在 32 位环境中地址空间不足所致。即使文件本身小于 2^32 字节,也可能会发生这种情况,这可能是由于缺乏足够的连续地址空间,或者由于 JVM 中的其他足够大的地址空间要求同时与大MappedByteBuffer
请求相结合(请参阅注释) . 需要明确的是,即使最初的原因是 ENOMEM,仍然可以抛出 IOE 而不是 OOM 。此外,尤其是较旧的 [在此处插入 Microsoft OS] 32 位环境(例如,示例)似乎存在问题。
所以看起来你有三个主要选择。
- 完全使用“ 64 位 JRE 或...另一个操作系统”。
- 使用不同类型的较小缓冲区并以块的形式对文件进行操作。(并且由于不使用映射缓冲区而受到性能影响)
- 出于性能原因继续使用
MappedFileBuffer
,但也以较小的块对文件进行操作以解决地址空间限制。
我将使用较小的块作为第三个的原因是因为在取消映射(示例MappedFileBuffer
)时存在公认且未解决的问题,这是您在处理每个块之间必须要做的事情,以避免达到 32 位由于累积映射的组合地址空间占用空间而导致的上限。 (注意:这仅适用于 32 位地址空间上限而不是一些内部操作系统限制问题...如果是后者,则忽略本段) 您可以尝试此策略MappedFileBuffer
(删除所有引用然后运行 GC),但您基本上将受制于 GC 和您的底层操作系统如何在内存映射文件方面进行交互。其他试图或多或少直接操作底层内存映射文件的潜在变通方法(示例)非常危险,并受到 Oracle 的特别谴责(参见最后一段)。最后,考虑到 GC 行为无论如何都是不可靠的,而且官方文档明确指出“内存映射文件的许多细节 [are] unspecified ”,我不建议您使用MappedFileBuffer
这样的解决方法,无论您可能读到什么解决方法.
因此,除非您愿意冒险,否则我建议您遵循 Oracle 的明确建议(第 1 点),或者使用不同的缓冲区类型将文件作为一系列较小的块处理(第 2 点)。