gpt4 book ai didi

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

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:54:18 26 4
gpt4 key购买 nike

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

原来我只是分配一个缓冲区

ByteBuffer audioData = ByteBuffer.allocateDirect((int)(fc.size() - fc.position()));

但有一段时间我把它作为
MappedByteBuffer mappedFile = fc.map(MapMode.READ_WRITE, 0, totalTargetSize);

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

Java Large File Upload throws java.io.IOException: Map failed

完整代码可以在 here 看到

最佳答案

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

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

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

那么这意味着什么尽可能的解决方案呢?

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


  • 另外, totalTargetSize究竟是什么?对于您的问题案例?

    编辑:

    经过一番挖掘,似乎很明显 IOException 是由于 running out of address space in a 32-bit environment .即使由于缺乏足够的连续地址空间,或者由于 JVM 中其他足够大的地址空间要求同时结合大 MappedByteBuffer,即使文件本身小于 2^32 字节,也会发生这种情况。请求 ( see comments )。需要明确的是,仍然可以抛出 IOE 而不是 OOM even if the original cause is ENOMEM .此外,特别是旧的 [在此处插入 Microsoft 操作系统] 32 位环境( exampleexample )似乎存在问题。

    所以看起来你有三个主要选择。
  • 一起使用“the 64-bit JRE or...another operating system”。
  • 使用不同类型的较小缓冲区并对文件进行分块操作。 (并因不使用映射缓冲区而降低性能)
  • 继续使用 MappedFileBuffer出于性能原因,但也以较小的块对文件进行操作,以解决地址空间限制。

  • 我使用 MappedFileBuffer 的原因在较小的块中作为第三个是因为在取消映射 MappedFileBuffer 时存在完善且 Unresolved 问题。 ( example ),这是您在处理每个块之间必须要做的事情,以避免由于累积映射的组合地址空间占用而达到 32 位上限。 (注意:这仅适用于 32 位地址空间上限而不是一些内部操作系统限制的问题......如果是后者,则忽略此段)您可以尝试 this strategy (删除所有引用,然后运行 ​​GC),但您基本上会受制于 GC 和您的底层操作系统如何在内存映射文件方面进行交互。其他试图或多或少直接操作底层内存映射文件 ( example ) 的潜在解决方法非常危险,并且受到 Oracle ( see last paragraph ) 的特别谴责。最后,考虑到 GC 行为无论如何都是不可靠的,而且官方文档明确指出“ many of the details of memory-mapped files [are] unspecified”,我会 不是 推荐使用 MappedFileBuffer无论您可能读到什么解决方法,都像这样。

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

    关于java - 在内存有限的系统上写入大文件时,如何避免 mapFailed() 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40545683/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com