为什么Java内存映射缓冲区会导致大量意外磁盘IO?

8

我编写了一些使用映射文件缓冲区的Posix程序。一个简单的场景是将一个1GB的文件映射到内存中,并用内容填满整个文件。

在程序执行期间,几乎没有磁盘IO操作,直到发生msyncmunmap调用。

在完全相同的系统上,我用Oracle JDK 7编写了等效的Java程序,并注意到整个程序执行期间有大量的磁盘IO活动。

JVM中的内存映射文件缓冲区实现方式有何不同?是否有任何方法可以延迟大量的IO活动?

操作系统是Linux 3.2 x64。

代码:

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class Main {
public static void main(String[] args) throws Exception {
    long size = 1024 * 1048576;
    RandomAccessFile raf= new RandomAccessFile("mmap1g", "rw");
    FileChannel fc = raf.getChannel();
    MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_WRITE, 0, size);
    for(long i = 0; i < size; ++i)
        buf.put((byte)1);
}
}

2
这可能与你编码的方式有关。如果没有看到代码,就无法判断。 - NPE
@NPE 如果你仔细关注过任何一个mmap程序,这是一个显而易见的现象。但我还是放上了代码。 - user972946
你可以使用long类型来代替,但你只需要触碰每一页(每4KB)就能更快地污染数据。查看MappedByteBuffer.load()的实现,它所做的就是读取每4KB。 - Peter Lawrey
你的JVM为内存映射分配了多少内存? - user2357112
@user2357112,限制您的内存映射的是32位JVM可用地址空间,特别是在Windows上,您的地址空间非常有限。 - Peter Lawrey
显示剩余3条评论
1个回答

10

内存映射完全由操作系统实现。JVM除了通过force()方法和选项文件中的"rws"选项以外,无法控制它如何刷新到磁盘。

Linux将根据在sysctl中设置的内核参数来刷新到磁盘。

$ sysctl -a | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500

这些是我笔记本电脑上的默认值。比率10表示当主要内存的10%为脏数据时,它将在后台开始将数据写入磁盘。20%的写回意味着写入程序将停止,直到脏百分比降至20%以下。无论如何,3000个厘秒或30秒后数据将写入磁盘。


一个有趣的比较是在tmpfs文件系统上对文件进行内存映射。我已将/tmp挂载为tmpfs,但大多数系统都有/dev/shm。


顺便说一句,您可能会发现这个类很有趣。MemoryStore允许您映射任何大小的内存,即>> 2 GB,并对其执行线程安全操作。例如,您可以在进程之间共享内存。它支持堆外锁定、易失性读/写、有序写和CAS。

我有一个测试,在其中两个进程锁定、切换、解锁记录,平均延迟为50纳秒。

顺便说一句,Linux有稀疏文件,这意味着您可以将区域映射到超出主内存但小于可用磁盘空间的大小。例如,如果您映射了8 TB并仅使用4 GB的随机片段,则它将使用最多4 GB的内存和4 GB的磁盘空间。如果您使用du {file},则可以查看实际使用的空间。注意:惰性分配磁盘空间可能会导致高度碎片化的文件,这可能是HDD的性能问题。


1
@Howard,将Java的本质放在一个地方看起来也很有用,而不是分散在许多不在src.zip中的类中。 - Peter Lawrey
1
@Howard 你可以从 MemoryStore 中创建 Bytes 的切片,然后将对象序列化/反序列化到其中,例如使用 ObjectInput/Output,或者将数据结构映射到其中。 - Peter Lawrey
我正在构建一个SharedHashMap,它基于MemoryStore(即完全在堆外),是并发的,可以在进程之间共享,并且可以通过TCP进行复制。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接