我有一个小而简单的存储系统,可以通过内存映射文件进行访问。由于我需要处理超过 2GB 的空间,因此我需要一个具有固定大小(如 2GB)的 MappedByteBuffer 列表(由于不同原因,我使用的空间较少)。那么一切都相对简单:一个缓冲区映射到某个空间,比如 1GB,当我需要更多时,我映射一个新的 MappedByteBuffer(文件自动增加),然后当我需要更多时,映射第三个缓冲区等。这就起作用了。
但后来我读到当我更改文件长度时可能会出现问题:
MappedByteBuffer 直接反映与其关联的磁盘文件。如果在映射生效时对文件进行结构修改,则可能会导致奇怪的行为(确切的行为取决于操作系统和文件系统) MappedByteBuffer 具有固定大小,但其映射到的文件是弹性的。具体来说,如果在映射生效时文件的大小发生变化,则部分或全部缓冲区可能变得无法访问,可能会返回未定义的数据,或者可能会引发未经检查的异常。当文件被内存映射时,要小心其他线程或外部进程如何操作文件。
我认为可能会出现问题,因为操作系统可以在文件增加时移动文件,然后 MappedByteBuffer 可能指向无效空间(或者我是否误解了这一点?)
因此,我现在不将新的 MappedByteBuffer 添加到列表中,而是执行以下操作
- 增加文件长度
- 清除缓冲区列表(扔掉旧缓冲区并希望通过垃圾收集器释放缓冲区。嗯,也许我应该通过 Cleaner.clean() 显式清理所有缓冲区?)
- 重新映射(用新缓冲区填充列表)
但这个过程的缺点是有时会在映射时失败
IOException: Operation not permitted
at sun.nio.ch.FileChannelImpl.map0(Native Method)
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:734)
为什么?因为清除缓冲区列表没有正确释放和清理缓冲区并且不允许多个映射?我是否应该坚持旧的工作方法而忽略书中的评论?
Update
- 在 32 位操作系统上拆分映射的优点是可以更好地找到可用空间并且不太可能出错(ref https://stackoverflow.com/a/258097/194609)
- 将映射分割成更小的部分是一个优点,因为设置 mmap 的成本可能很高(ref https://stackoverflow.com/a/2895799/194609)
- 这两种方法都不干净,而我的第二种方法应该可以工作,但需要取消映射(将尝试使用正常的cleaner.clean hack强制释放)。第一种方法应该适用于系统(例如ibm http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/apis/mmap.htm)我可以增加文件大小,但一般来说它不起作用,尽管我还找不到确切的原因......
- 我担心最干净的方法是使用多个文件(每个 MappedByteBuffer 一个文件)
根本原因是我的错:我不小心过于频繁地重新映射底层文件(容量仅通过小步骤增加)。
但即使在这种极端情况下,当我重试失败的映射操作时,我最终还是能够修复 IOException(不允许操作)(+ System.gc + 5ms 睡眠 -> 这应该让 jvm 有机会取消映射缓冲区) 。现在我只看到了大量的重新映射,从而得出了最终的结论。
至少我对 mmap 有了更多的了解:它非常依赖操作系统+文件系统 - 也感谢auselen https://stackoverflow.com/users/1163019/auselen!如果您喜欢干净的解决方案,您应该按照他最初的建议为每个文件使用一个 MappedByteBuffer 。但如果您需要大空间并且操作系统文件描述符限制太低,这也可能会出现问题。
最后但并非最不重要的一点是,我强烈建议不要使用我的第一个解决方案,因为我无法找到保证(仅在 IBM 操作系统中;))在文件大小增加后使映射缓冲区保持完整。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)