2

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

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

MappedByteBuffer 直接反映与其关联的磁盘文件。如果在映射生效时文件在结构上被修改,可能会导致奇怪的行为(具体行为取决于操作系统和文件系统) MappedByteBuffer 具有固定大小,但它映射到的文件是弹性的。具体来说,如果在映射生效时文件的大小发生变化,则部分或全部缓冲区可能变得不可访问,可能会返回未定义的数据,或者可能会引发未经检查的异常。请注意文件在内存映射时如何被其他线程或外部进程操作。

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

因此,我现在没有向列表中添加新的 MappedByteBuffer,而是执行以下操作

  1. 增加文件长度
  2. 清除缓冲区列表(丢弃旧缓冲区并希望通过垃圾收集器释放缓冲区。嗯,也许我应该通过cleaner.clean()显式清除所有缓冲区?)
  3. 重新映射(用新的缓冲区填充列表)

但是这个过程的缺点是有时会在映射时失败

IOException: Operation not permitted
    at sun.nio.ch.FileChannelImpl.map0(Native Method)
    at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:734)

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

更新

  • 在 32 位操作系统上拆分映射的优点是可以更好地找到可用空间并且不太可能出错(参考
  • 将映射分成更小的部分是一个优势,因为设置 mmap 可能成本高昂(参考
  • 这两种方法都不干净,而我的第二种方法应该可以工作,但需要取消映射(将尝试使用正常的 cleaner.clean hack 强制释放)。第一种方法应该适用于我可以增加文件大小的系统(如ibm),但总的来说它不起作用,尽管我还找不到确切的原因......
  • 最干净的方法是使用我担心的多个文件(每个 MappedByteBuffer 一个文件)
4

1 回答 1

1

根本原因是我的错:不小心我过于频繁地重新映射底层文件(容量仅通过小步骤增加)。

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

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

最后但并非最不重要的一点是,我强烈建议不要使用我的第一个解决方案,因为我找不到保证(仅在 IBM OS 中;))在文件大小增加后保持映射缓冲区完好无损。

于 2012-12-23T22:15:56.230 回答