java でファイルマップを行ったときの動きについて
Javaでファイルマップを行ったときの動きについて調査したのでまとめる。
環境 CentOS7.3.1611、openjdk-1.8.0.131
C言語でopen+mmapシステムコールを利用し、ファイルをメモリ上にマップすることが出来るが、Javaでの利用が以前から気になっていたが
実際に試してみる。
以下のソースで評価する。
mmap01.java
import java.lang.Thread;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class mmap01
{
public static void main(String[] args) throws Exception
{
for (int argc = 0; argc < args.length; argc++ ) {
File file = new File(args[argc]);
long length = file.length();
System.out.println("file:" + args[0] + " size:" + length);
MappedByteBuffer in = new RandomAccessFile(file, "r")
.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, length);
for (long i = 0; i < length; i++) {
in.get();
}
System.out.println("Finished reading");
}
System.out.println("wait");
Thread.sleep(1000000);
}
}
実際にマップしてみる。
ちなみに、Integer.MAX_VALUE を越える(2^31)ファイルの場合は、mapのタイミングで以下のようなエラーになる。
2^31-1のサイズではマップできた。
$ dd if=/dev/zero of=2gb bs=1 seek=2147483647 count=1
$ dd if=/dev/zero of=2gb-1 bs=1 seek=2147483646 count=1
$ dd if=/dev/zero of=2gb-2 bs=1 seek=2147483645 count=1
$ java mmap01 2gb
file:2gb size:2147483648
Exception in thread "main" java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:869)
at mmap01.main(mmap01.java:17)
$ java mmap01 2gb-1
file:2gb-1 size:2147483647
Finished reading
wait
この時の、javaプロセスのメモリマップ状態とJavaの状態を確認すると以下のようになっている。mmapでマップされているようだ。
jstatの結果からいくと、マップした領域はヒープの対象外のようだ。
$ cat /proc/$(pidof java)/maps
(略)
7f97c8000000-7f9848000000 r--s 00000000 00:26 5 /mnt/hgfs/COMMON/Java_Mmap/2gb-1
(略)
$ jstat -gc $(pidof java)
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 671.9 20480.0 0.0 4480.0 866.3 384.0 73.9 0 0.000 0 0.000 0.000
複数ファイルマップしたときはどうなるか。
$ java mmap01 2gb-1 2gb-2
file:2gb-1 size:2147483647
Finished reading
file:2gb-1 size:2147483646
Finished reading
wait
きちんと2ファイルともマップされている。ヒープも追加で読み込んだ領域(2^31-2)ほど増えていない。
$ cat /proc/$(pidof java)/maps
(略)
7fd08c000000-7fd10c000000 r--s 00000000 00:26 6 /mnt/hgfs/COMMON/Java_Mmap/2gb-2
7fd10c000000-7fd18c000000 r--s 00000000 00:26 5 /mnt/hgfs/COMMON/Java_Mmap/2gb-1
(略)
$ jstat -gc $(pidof java)
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 835.7 20480.0 0.0 4480.0 866.3 384.0 73.9 0 0.000 0 0.000 0.000
それにしても、なぜマップ可能な領域を2GBの制限を設けたのだろうか。
また、明示的なアンマップ方法が無いのも扱いが難しい。DirectByteBufferの注意点とパフォーマンス比較でも解放の記述がある。