ページ更新: 2009-12-10 (木) (3506日前)

(新規作成: 2004-07-15)

Java2 SE 1.4 で追加された nio についてのメモ。

なお、Java SE 7あたりで nio2 が追加されるようだ。nioでは不十分な機能があったらしい。

目次

[編集]

情報源 #

IBM developerWorks:

Java in the Box:

マイコミジャーナル:

[編集]

DirectByteBuffer の領域の大きさを指定する (2004-10-14) #

DirectByteBuffer の領域の大きさを指定するコマンドラインオプション。文書化されていないらしい。

java -XX:MaxDirectMemorySize=10m 実行するクラス名
[編集]

メモ #

[編集]

MappedByteBufferを廃棄するには (2004-07-15, 2009-10-22) #

  • MappedByteBufferを、短時間にたくさん使いたい (秒1000回くらい)。 しかし、Java 2 SE 1.4.x, 1.5.xでは、メモリマップ用のメモリ領域を使用後に開放できないため、「java.io.IOException: このコマンドを実行するのに十分な記憶域がありません。」というエラーが出る

メモリマップ用の領域は有限なので、不要になったら廃棄したい。

  • 廃棄するメソッドはないようだ。
  • 同一のファイルに対しての読み込みアクセスであれば使い回せるので、SoftReferenceを使ってキャッシュしておく。
  • 2009-10-22 : 新しいJDK + Windows XP SP3 + Intel Core2Quad Q6700 を使って、動作を確認した。
  • J2SDK 1.4.2_18, JDK 1.5.0_18では、ガーベッジコレクションを実行すると、廃棄されることがある。廃棄されないこともある。
    • つまり、ガーベッジコレクションを行っても、回避できない
      • 以前 (2004〜2005年頃) の環境 (その当時のJDK + Windows XP SP2 + Intel Pentium 4 (HT=on)) では、回避できたと思うのだが。
  • JDK 1.6.0_16では自動的に廃棄される。 (なお、以前にJava SE 6.0 b2で試したときも同様だった)

以下、挙動を確認するためのコード:

// -*-mode:java; coding:utf-8;-*-

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public final class MappedBufferLimit {

    public static void main(final String[] args) {
        System.out.println("java.runtime.version=" + System.getProperty("java.runtime.version"));

        try {
            final int loops = 20;
            final int fileSize = Integer.MAX_VALUE / loops;

            // メモリにマップするファイルを作成する。
            final File file = createTestFile(fileSize);
            file.deleteOnExit();

            // 同一のファイル(path)を指定した回数 (LOOPS) だけ、メモリに割り付ける。
            // Mapしてエラーが出たらGCしてリトライする。
            doMapAndGc(file, loops);

            // Mapして例外が出たら、あきらめる。
            doMap(file, loops);

        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    private static File createTestFile(final int fileSize) throws IOException {
        final File file = File.createTempFile("map-test", "dat");
        System.out.println("testfile=" + file.getAbsolutePath());

        final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
        try {
            final int chunkSize = 8 * 1024;
            final int loops = fileSize / chunkSize;
            final byte[] buf = new byte[chunkSize];
            for (int i = 0; i < loops; i++) {
                bos.write(buf);
            }

            bos.flush();

        } finally {
            bos.close();
        }

        return file;
    }

    /**
     * 同一のファイルを指定した回数だけ、メモリに割り付ける。
     * 例外が発生したら、ガーベッジコレクションを行った後、再度メモリに割り付ける。
     */
    private static void doMapAndGc(final File file, final int loops) throws IOException {
        System.out.println("---------------------------------------- doMapAndGc");
        final Runtime runtime = Runtime.getRuntime();

        for (int i = 1, s = loops; i <= s; i++) {
            System.out.println("doMapAndGc: count=" + i);

            MappedByteBuffer b;
            try {
                b = createMappedFile(file);

            } catch (final IOException e) {
                System.out.println("retry");
                runtime.gc();
                b = createMappedFile(file);
            }

            System.out.println("doMapAndGc: buffer=" + b);
        }
    }

    /**
     * 同一のファイルを指定した回数だけ、メモリに割り付ける。
     * 例外が発生したら中断する。
     */
    private static void doMap(final File file, final int loops) throws IOException {
        System.out.println("---------------------------------------- doMap");

        long limitSum = 0;
        long capacitySum = 0;
        for (int i = 1, s = loops; i <= s; i++) {
            System.out.println("doMap: count=" + i);
            MappedByteBuffer b = createMappedFile(file);

            limitSum += b.limit();
            capacitySum += b.capacity();
            System.out.println(
                    " doMap: limitSum=" + limitSum
                    + " capacitySum=" + capacitySum
                    + " buffer=" + b);
        }
    }

    /** ファイルをメモリに割り付ける */
    private static MappedByteBuffer createMappedFile(final File file)
    throws IOException, FileNotFoundException {
        final FileInputStream fis = new FileInputStream(file);
        final FileChannel channel = fis.getChannel();
        final long size = channel.size();
        final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
        return buffer;
    }
}
  • 2009-12-10 createMappedFile でFileInputStream#close() とFileChannel#close() を呼んでなかったので、テストをやり直す必要がある。 (なお、実際に使っているコードの方では、どちらもfinallyでclose()してた)

いろいろなJDKでコンパイルして実行するためのバッチファイル (MappedBufferLimit.cmd):

c:\java\j2sdk1.4.2_18\bin\javac MappedBufferLimit.java
c:\java\j2sdk1.4.2_18\bin\java MappedBufferLimit

c:\java\jdk1.5.0_18\bin\javac -encoding utf-8 MappedBufferLimit.java
c:\java\jdk1.5.0_18\bin\java MappedBufferLimit

c:\java\jdk1.6.0_16\bin\javac -encoding utf-8 MappedBufferLimit.java
c:\java\jdk1.6.0_16\bin\java MappedBufferLimit

以下のようにしてバッチファイルを実行する:

C:> (MappedBufferLimit.cmd 2>&1) > result.txt

結果 (result.txt)。一部を省略している。なお、この結果には現れていないが、手元では J2SDK 1.4.2_18 と JDK 1.5.0_16では実行する毎にretryの成功/失敗が変わる:

C:> c:\java\j2sdk1.4.2_18\bin\javac MappedBufferLimit.java

C:> c:\java\j2sdk1.4.2_18\bin\java MappedBufferLimit
java.runtime.version=1.4.2_18-b06
testfile=C:\Temp\map-test44811dat
---------------------------------------- doMapAndGc
doMapAndGc: count=1
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]

 : (略)

doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMapAndGc: count=15
retry                                                                      ★retry (gc実行) したが、
java.io.IOException: このコマンドを実行するのに十分な記憶域がありません。  ★回避できなかった。
	at sun.nio.ch.FileChannelImpl.map0(Native Method)
	at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:705)
	at MappedBufferLimit.createMappedFile(MappedBufferLimit.java:112)
	at MappedBufferLimit.doMapAndGc(MappedBufferLimit.java:77)
	at MappedBufferLimit.main(MappedBufferLimit.java:27)


C:> c:\java\jdk1.5.0_18\bin\javac -encoding utf-8 MappedBufferLimit.java

C:> c:\java\jdk1.5.0_18\bin\java MappedBufferLimit
java.runtime.version=1.5.0_18-b02
testfile=C:\Temp\map-test2712936866145569816dat
---------------------------------------- doMapAndGc
doMapAndGc: count=1
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]

 : (略)

doMapAndGc: count=12
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMapAndGc: count=13
retry                                                                             ★retry (gc実行)して、
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]  ★回避できた
doMapAndGc: count=14
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]

 : (略)

doMapAndGc: count=20
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
---------------------------------------- doMap
doMap: count=1
 doMap: limitSum=107372544 capacitySum=107372544 buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMap: count=2
 doMap: limitSum=214745088 capacitySum=214745088 buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMap: count=3
 doMap: limitSum=322117632 capacitySum=322117632 buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMap: count=4
 doMap: limitSum=429490176 capacitySum=429490176 buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMap: count=5
java.io.IOException: このコマンドを実行するのに十分な記憶域がありません。
	at sun.nio.ch.FileChannelImpl.map0(Native Method)
	at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:742)
	at MappedBufferLimit.createMappedFile(MappedBufferLimit.java:112)
	at MappedBufferLimit.doMap(MappedBufferLimit.java:95)
	at MappedBufferLimit.main(MappedBufferLimit.java:30)


C:> c:\java\jdk1.6.0_16\bin\javac -encoding utf-8 MappedBufferLimit.java

C:> c:\java\jdk1.6.0_16\bin\java MappedBufferLimit
java.runtime.version=1.6.0_16-b01
testfile=C:\Temp\map-test4799407424205691796dat
---------------------------------------- doMapAndGc
doMapAndGc: count=1
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]

 : (略)                                                                            ★IOExceptionが発生しない

doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
doMapAndGc: count=20
doMapAndGc: buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]
---------------------------------------- doMap
doMap: count=1
 doMap: limitSum=107372544 capacitySum=107372544 buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]

 : (略)                                                                            ★IOExceptionが発生しない

doMap: count=20
 doMap: limitSum=2147450880 capacitySum=2147450880 buffer=java.nio.DirectByteBufferR[pos=0 lim=107372544 cap=107372544]