[英]Truncate memory mapped file
我正在使用内存映射IO作为索引文件,但问题是如果文件大部分为空,我无法调整文件大小。
之前的某个地方:
MappedByteBuffer map = raf.getChannel().map(MapMode.READ_WRITE, 0, 1 << 30);
raf.close();
// use map
map.force();
map = null;
调整:
for (int c = 0; c < 100; c++) {
RandomAccessFile raf = new RandomAccessFile(indexFile, "rw");
try {
raf.setLength(newLen);
if (c > 0) LOG.warn("used " + c + " iterations to close mapped byte buffer");
return;
} catch (Exception e) {
System.gc();
Thread.sleep(10);
System.runFinalization();
Thread.sleep(10);
} finally {
raf.close();
}
}
当使用Windows或Linux 32位时,我经常遇到解映问题,但在64位Linux生产环境中,一切似乎都没有警告,但文件保持原始大小。
任何人都可以解释为什么会发生这种情况和/或如何解决问题?
您的问题是您使用不可靠的方法来关闭映射的字节缓冲区(一百次调用System.gc()
和System.runFinalization()
不保证任何东西)。 遗憾的是,Java API中没有可靠的方法可以做到这一点,但是在Sun JVM上(也可能在其他一些方面),您可以使用以下代码:
public void unmapMmaped(ByteBuffer buffer) {
if (buffer instanceof sun.nio.ch.DirectBuffer) {
sun.misc.Cleaner cleaner = ((sun.nio.ch.DirectBuffer) buffer).cleaner();
cleaner.clean();
}
}
当然它依赖于JVM,如果Sun决定以不兼容的方式更改sun.nio.ch.DirectBuffer
或sun.misc.Cleaner
,你应该准备修复你的代码(但实际上我不相信这会永远发生)。
这只是对前一个答案的补充,这是完全正确的。
JDK 1.7抱怨使用sun.misc.Cleaner
,说这个命名空间中的类不是JDK的正式部分,并且可能在将来消失。 但是,从1.7开始,它们仍然存在。
如果.clean()
方法不可用,那么使用System.gc()
可以用作回退方法,但是必须将其确认为“hack”,因此必须小心。
虽然System.gc()
不能强制关闭未引用的映射,但实际上它通常会导致清理。 32位Linux(和Solaris)的经验表明,在第一次或第二次调用System.gc()
期间,每次测试期间都会释放缓冲区。 但是,Windows上的行为是不同的。 在大多数情况下,所有映射都在第二次调用System.gc()
,但有时需要3次调用。 仍然存在需要更多呼叫的情况,需要更多频率的呼叫减少。 这可能具有欺骗性,因为测试可能表明只需要4次调用,但只能让它在一个月后失败。 5个电话似乎足够,只会在6个月内导致失败。
测试是否已发布映射可以通过使用FileChannel.truncate()
周围的try/catch
块来完成,并使用循环重新尝试失败时的操作。 循环不能是无限的,因为存在特定堆配置将导致垃圾收集器永远不会清理映射的病态情况。 然而,大约10的循环将涵盖几乎所有情况。 如果对象没有消失,那么它就不会去任何地方,应用程序将不得不放弃。 这似乎是不合适的,但在实践中,它极不可能,并且只会在不支持清洁工的JVM上出现问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.