更改文件长度时,是否需要重新映射所有关联的MappedByteBuffers?

时间:2012-12-23 14:52:54

标签: java nio mmap memory-mapped-files

我有一个小而简单的存储系统,可通过内存映射文件访问。由于我需要处理超过2GB的空间,我需要一个MappedByteBuffer列表,其固定大小为2GB(由于不同的原因我使用较少)。然后所有都是相对简单的:一个缓冲区映射到某个空间说1GB,当我需要更多时,我映射一个新的MappedByteBuffer(文件自动增加),然后当我需要更多时,第三个缓冲区被映射等等。这只是起作用。 / p>

但后来我在Java NIO book中读到,当我更改文件长度时可能会出现问题:

  

MappedByteBuffer直接反映与之关联的光盘文件。如果在映射生效时对文件进行结构修改,则可能会导致奇怪的行为(确切的行为是操作系统和文件系统相关的)MappedByteBuffer具有固定的大小,但它映射到的文件是弹性的。特别是如果文件的大小在映射生效时发生更改,则部分或全部缓冲区可能无法访问,可能会返回未定义的数据,或者可能会抛出未经检查的异常。在内存映射时,请注意文件如何被其他线程或外部进程操纵。

我认为问题可能发生,因为操作系统可能会在文件增加时移动文件,然后MappedByteBuffers会指向无效空间(或者我是否误解了这个?)

所以,我没有在列表中添加新的MappedByteBuffer,而是在进行以下操作

  1. 增加文件长度
  2. 清除缓冲区列表(抛弃旧缓冲区并希望缓冲区通过垃圾收集器释放。嗯,可能我应该通过cleaner.clean()显式清除所有缓冲区?)
  3. 重新映射(用新缓冲区填充列表)
  4. 但是这个程序的缺点是在使用

    进行映射时有时会失败
    IOException: Operation not permitted
        at sun.nio.ch.FileChannelImpl.map0(Native Method)
        at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:734)
    

    为什么?因为清除缓冲区列表没有正确释放和清理缓冲区并且不允许多个映射?我应该坚持使用旧的工作方法并忽略书中的评论吗?

    更新

    • 在32位操作系统上拆分映射具有更好地查找可用空间而不太可能发生错误的优势(ref
    • 将映射拆分为较小的部分是一个优势,因为设置mmap可能成本很高(ref
    • 这两种方法都不干净,而我的第二种方法应该可以工作,但是需要一个unmap(将尝试用普通的cleaner.clean hack强制释放)。第一种方法应该适用于系统(如ibm),我可以增加文件大小,但一般情况下它不会工作,虽然我找不到确切的原因......
    • 最干净的方法是使用我担心的多个文件(每个MappedByteBuffer一个文件)

1 个答案:

答案 0 :(得分:1)

根本原因是我的错:不小心我经常重新映射底层文件(容量只通过迷你步骤增加)。

但即使在这种极端情况下,当我重试失败的映射操作时,我能够最终修复IOException(操作不允许)(+ System.gc + 5ms睡眠 - >这应该给jvm机会取消映射缓冲区)。现在我只看到大量的重映射,最终得出结论。

至少我对mmap有了更多的了解:它非常依赖OS +文件系统 - 还要感谢auselen!如果你喜欢一个干净的解决方案,你应该按照他最初建议的每个文件使用一个MappedByteBuffer。但如果您需要大空间且操作系统文件描述符限制太低,这也可能会出现问题。

最后但并非最不重要的是,我强烈建议不要使用我的第一个解决方案,因为我找不到保证(仅在IBM操作系统;)),这会在文件大小增加后保留映射缓冲区。