我有一个小而简单的存储系统,可以通过内存映射文件访问。因为我需要处理超过 2GB 的空间,所以我需要一个固定大小的 MappedByteBuffer 列表,例如 2GB(由于不同的原因,我使用的较少)。然后一切都相对简单:一个缓冲区映射到某个空间,比如 1GB,当我需要更多时,我映射一个新的 MappedByteBuffer(文件自动增加),然后当我需要更多时,映射第三个缓冲区等。这就奏效了。
但后来我读到了 Java NIO book当我更改文件长度时可能会出现问题:
A MappedByteBuffer directly reflects the disc file with which it is associated. If the file is structurally modified while the mapping is in effect, strange behaviour can result (exact behaviour are OS and file system dependent) A MappedByteBuffer has a fixed size, but file it's mapped to is elastic. Specifically if a file's size changes while the mapping is in effect, some or all of the buffer may become inaccessible, undefined data could be returned, or unchecked exceptions could be thrown. Be careful about how files are manipulated by other threads or external processes when they are memory-mapped.
我认为问题可能会发生,因为操作系统可能会在文件增加时移动文件,然后 MappedByteBuffers 可能会指向无效空间(或者我是否误解了这一点?)
因此,我现在没有向列表中添加新的 MappedByteBuffer,而是执行以下操作
- 增加文件长度
- 清除缓冲区列表(丢弃旧缓冲区并希望通过垃圾收集器释放缓冲区。嗯,也许我应该通过 cleaner.clean() 显式清除所有缓冲区?)
- 重新映射(用新缓冲区填充列表)
但是这个过程的缺点是在映射时有时会失败
IOException: Operation not permitted
at sun.nio.ch.FileChannelImpl.map0(Native Method)
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:734)
为什么?因为清除缓冲区列表没有正确释放和清理缓冲区并且不允许多个映射?是不是我要固守旧的工作方法而忽略书中的评论呢?
更新
最佳答案
根本原因是我的错:不小心我过于频繁地重新映射底层文件(容量仅通过微小的步骤增加)。
但即使在这种极端情况下,当我重试失败的映射操作(+ System.gc + 5ms sleep -> 这应该让 jvm 有机会取消映射时,我终于能够修复 IOException(不允许操作)缓冲区)。现在我只看到大量的重新映射导致最终结论。
至少我学到了更多关于 mmap 的知识:它非常依赖操作系统+文件系统 - 还要感谢 auselen !如果你喜欢一个干净的解决方案,你应该按照他最初的建议为每个文件使用一个 MappedByteBuffer 。但是,如果您需要大空间并且您的操作系统文件描述符限制太低,这也可能会出现问题。
最后但同样重要的是,我强烈建议不要使用我的第一个解决方案,因为我找不到保证(仅在 IBM 操作系统中;))在文件大小增加后保持映射缓冲区完好无损。
关于java - 更改文件长度时,是否需要重新映射所有关联的 MappedByteBuffer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14011919/