java でファイルマップを行ったときの動きについて

# java でファイルマップを行ったときの動きについて

Javaでファイルマップを行ったときの動きについて調査したのでまとめる。

環境 CentOS7.3.1611、openjdk-1.8.0.131

C言語でopen+mmapシステムコールを利用し、ファイルをメモリ上にマップすることが出来るが、Javaでの利用が以前から気になっていたが
実際に試してみる。

以下のソースで評価する。

```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の注意点とパフォーマンス比較](http://d.hatena.ne.jp/seraphy/20151120)でも解放の記述がある。

## 参考

 * [Java NIO – Memory-Mapped Files with MappedByteBuffer](http://howtodoinjava.com/java-7/nio/java-nio-2-0-memory-mapped-files-mappedbytebuffer-tutorial/)
 * [Memory-mapping >2gb of data in Java](http://nyeggen.com/post/2014-05-18-memory-mapping-%3E2gb-of-data-in-java/)
 * [続:memory mapped file が消せない](http://d.hatena.ne.jp/odz/20060629/1151553870)
 * [How to unmap a file from memory mapped using FileChannel in java?](http://stackoverflow.com/questions/2972986/how-to-unmap-a-file-from-memory-mapped-using-filechannel-in-java)
 * [DirectByteBufferの注意点とパフォーマンス比較](http://d.hatena.ne.jp/seraphy/20151120)