截断内存映射文件

我正在使用内存映射IO的索引文件,但问题是,我不能resize为空的文件。

某处之前:

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生产环境中,一切似乎都没有任何警告,但是文件保持原来的大小。

任何人都可以解释为什么会发生这种情况和/或如何解决这个问题?

Solutions Collecting From Web of "截断内存映射文件"

你的问题是你正在使用不可靠的方法来关闭映射的字节缓冲区(对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.DirectBuffersun.misc.Cleaner ,则应该准备好修复代码(但实际上我不相信发生)。

这只是对上一个答案的补充,这是完全正确的。

JDK 1.7抱怨使用了sun.misc.Cleaner ,说这个命名空间中的类不是JDK的正式部分,并且可能在将来消失。 但是,从1.7开始,他们仍然存在。

如果.clean()方法不可用,那么可以使用System.gc()作为后备方法,但是必须确认这是一个“黑客”,因此必须谨慎使用。

虽然System.gc()不能强制关闭未引用的映射,但实际上它经常会导致清理的发生。 在32位Linux(和Solaris)上的经验显示,在第一次或第二次调用System.gc()期间,在每个测试期间释放缓冲区。 但是,在Windows上的行为是不同的。 在大多数情况下,第二次调用System.gc()结束时释放所有映射,但有时需要3次调用。 还有一些情况需要更多的呼叫,要求呼叫次数越来越少。 这可能是欺骗性的,因为测试可能表明4个电话都是必需的,只是在一个月之后让你失败。 5个电话似乎是足够的,只会导致6个月内的失败。

通过在FileChannel.truncate()周围使用try/catch块,可以通过一个循环来重新尝试失败时的操作,以测试是否已经释放映射。 循环不能是无限的,因为有病态的情况下,特定的堆配置将导致垃圾收集器永远不会清理映射。 但是,大约10个循环将覆盖几乎所有情况。 如果这个对象没有被删除,那么它就不会去任何地方,应用程序将不得不放弃。 这看起来似乎不够,但实际上,这是不太可能的,而且只会成为JVM上不支持清洁工的问题。