13

我正在为索引文件使用内存映射 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 或 32 位 Linux 时,我经常遇到取消映射问题,但在 64 位 Linux 生产环境中,一切似乎都可以正常工作,但文件保持原始大小。

谁能解释为什么会发生这种情况和/或如何解决问题?

4

2 回答 2

8

您的问题是您使用不可靠的方法来关闭映射的字节缓冲区(一百次调用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以不兼容的方式(但实际上我不相信这会发生),您应该准备好修复您的代码。

于 2011-07-06T18:51:06.313 回答
3

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

JDK 1.7 抱怨使用sun.misc.Cleaner,称此命名空间中的类不是 JDK 的正式部分,将来可能会消失。但是,从 1.7 开始,它们仍然存在。

如果该.clean()方法不可用,则 usingSystem.gc()可以用作备用方法,但是必须承认这是一种“hack”,因此必须小心使用。

虽然System.gc()不能强制关闭未引用的映射,但实际上它通常会导致清理发生。在 32 位 Linux(和 Solaris)上的经验表明,在第一次或第二次调用System.gc(). 但是,Windows 上的行为是不同的。在大多数情况下,所有映射都会在第二次调用结束时释放System.gc(),但有时需要 3 次调用。仍然存在需要更多呼叫的情况,对更高数量呼叫的要求频率会降低。这可能具有欺骗性,因为测试可能表明只需要 4 次调用,但一个月后它就会失败。5 个电话可能看起来就足够了,但只会导致 6 个月内失败。

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

于 2012-05-29T15:24:35.720 回答