53

を使用してファイル ("sample.txt") をメモリにマッピングしFileChannel.map()、次に を使用してチャネルを閉じていますfc.close()。この後、FileOutputStream を使用してファイルに書き込むと、次のエラーが発生します。

java.io.FileNotFoundException: sample.txt (要求された操作は、ユーザー マップ セクションが開いているファイルに対して実行できません)

File f = new File("sample.txt");
RandomAccessFile raf = new RandomAccessFile(f,"rw");
FileChannel fc = raf.getChannel();
MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
fc.close();
raf.close();

FileOutputStream fos = new FileOutputStream(f);
fos.write(str.getBytes());
fos.close();

これは、ファイルを閉じた後でもファイルがまだメモリにマップされていることが原因であると推測されますFileChannel。私は正しいですか?もしそうなら、どうすればメモリからファイルを「アンマップ」できますか? (API でこれを行う方法が見つかりません)。ありがとう。

編集: (unmap メソッドの追加) は RFE として Sun に送信されたようです: http://bugs.sun.com/view_bug.do?bug_id=4724038

4

13 に答える 13

38

次の静的メソッドを使用できます。

public static void unmap(MappedByteBuffer buffer)
{
   sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
   cleaner.clean();
}

しかし、これは次の理由から安全でない解決策です:
1) マップ解除後に誰かが MappedByteBuffer を使用すると失敗につながる
2) MappedByteBuffer 実装の詳細に依存する

于 2011-02-17T23:25:59.153 に答える
15

MappedByteBufferjavadocから:

マップされたバイト バッファーとそれが表すファイル マッピングは、バッファー自体がガベージ コレクションされるまで有効なままです。

電話してみるSystem.gc()?それも VM への提案にすぎません。

于 2010-06-04T09:58:13.940 に答える
5

sun.misc.Cleaner javadoc は次のように述べています。

汎用ファントム参照ベースのクリーナー。クリーナーは、軽量で堅牢なファイナライズの代替手段です。これらは VM によって作成されないため、JNI アップコールを作成する必要がなく、クリーンアップ コードがファイナライザー スレッドではなく参照ハンドラー スレッドによって直接呼び出されるため、軽量です。これらは、参照オブジェクトの最も弱いタイプであるファントム参照を使用するため、より堅牢です。これにより、ファイナライズに固有の厄介な順序付けの問題が回避されます。クリーナーは参照オブジェクトを追跡し、任意のクリーンアップ コードのサンクをカプセル化します。クリーナーの参照先がファントム到達可能になったことを GC が検出してからしばらくすると、参照ハンドラー スレッドがクリーナーを実行します。クリーナーは直接呼び出すこともできます。これらはスレッド セーフであり、サンクを最大 1 回実行することが保証されます。クリーナーはファイナライズに代わるものではありません。これらは、クリーンアップ コードが非常に単純でわかりやすい場合にのみ使用してください。重要なクリーナーは、参照ハンドラー スレッドをブロックし、さらなるクリーンアップとファイナライズを遅らせるリスクがあるため、お勧めできません。

System.gc() を実行することは、バッファの合計サイズが小さい場合は許容できる解決策ですが、ギガバイトのファイルをマッピングする場合は、次のように実装しようとします。

((DirectBuffer) buffer).cleaner().clean()

しかし!クリーニング後にそのバッファーにアクセスしないようにしてください。そうしないと、次のようになります。

Java ランタイム環境によって致命的なエラーが検出されました: EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000002bcf700, pid=7592, tid=10184 JRE バージョン: Java(TM) SE ランタイム環境 (8.0_40-b25) (ビルド 1.8.0_40- b25) Java VM: Java HotSpot(TM) 64 ビット サーバー VM (25.40-b25 混合モード windows-amd64 圧縮 oops) 問題のあるフレーム: J 85 C2 java.nio.DirectByteBuffer.get(I)B (16 バイト) @ 0x0000000002bcf700 [0x0000000002bcf6c0+0x40] コアダンプの書き込みに失敗しました。クライアント バージョンの Windows では、デフォルトでミニダンプが有効になっていません 詳細情報を含むエラー レポート ファイルは、C:\Users\?????\Programs\testApp\hs_err_pid7592.log に保存されます。 nio.DirectByteBuffer::get (16 バイト) ヒープの合計 [0x0000000002bcf590,0x0000000002bcf828] = 664 再配置 [0x0000000002bcf6b0,
[0x0000000002bcf760,0x0000000002bcf778] = 24 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8 metadata
[0x0000000002bcf780,0x0000000002bcf798] = 24 scopes data
[0x0000000002bcf798,0x0000000002bcf7e0] = 72 scopes pcs
[0x0000000002bcf7e0,0x0000000002bcf820] = 64 dependencies
[0x0000000002bcf820,0x0000000002bcf828] = 8

幸運を!

于 2015-07-23T16:21:54.537 に答える
4

採用している他の回答でカバーされている方法は、警告((DirectBuffer) byteBuffer).cleaner().clean()を表示せずに JDK 9+ では (リフレクション形式であっても) 機能しません。An illegal reflective access operation has occurredこれは、将来のJDKバージョンで完全に機能しなくなります。幸いなsun.misc.Unsafe.invokeCleaner(ByteBuffer)ことに、警告なしでまったく同じ呼び出しを行うことができます: (OpenJDK 11 ソースから):

public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
    if (!directBuffer.isDirect())
        throw new IllegalArgumentException("buffer is non-direct");

    DirectBuffer db = (DirectBuffer)directBuffer;
    if (db.attachment() != null)
        throw new IllegalArgumentException("duplicate or slice");

    Cleaner cleaner = db.cleaner();
    if (cleaner != null) {
        cleaner.clean();
    }
}

クラスsun.miscであるため、ある時点で削除されます。興味深いことに、これ以外のすべての呼び出しは にsun.misc.Unsafe直接プロキシされjdk.internal.misc.Unsafeます。が他のすべてのメソッドと同じ方法でプロキシされない理由はわかりません。JDK 15 の時点でinvokeCleaner(ByteBuffer)メモリ参照 (インスタンスを含む) を直接解放する新しい方法があるため、省略された可能性があります。DirectByteBuffer

JDK 7/8 および JDK 9+ で DirectByteBuffer/MappedByteBuffer インスタンスをクリーン/クローズ/マップ解除できる次のコードを作成しましたが、これによりリフレクション警告が表示されません。

private static boolean PRE_JAVA_9 = 
        System.getProperty("java.specification.version","9").startsWith("1.");

private static Method cleanMethod;
private static Method attachmentMethod;
private static Object theUnsafe;

static void getCleanMethodPrivileged() {
    if (PRE_JAVA_9) {
        try {
            cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
            cleanMethod.setAccessible(true);
            final Class<?> directByteBufferClass =
                    Class.forName("sun.nio.ch.DirectBuffer");
            attachmentMethod = directByteBufferClass.getMethod("attachment");
            attachmentMethod.setAccessible(true);
        } catch (final Exception ex) {
        }
    } else {
        try {
            Class<?> unsafeClass;
            try {
                unsafeClass = Class.forName("sun.misc.Unsafe");
            } catch (Exception e) {
                // jdk.internal.misc.Unsafe doesn't yet have invokeCleaner(),
                // but that method should be added if sun.misc.Unsafe is removed.
                unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
            }
            cleanMethod = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
            cleanMethod.setAccessible(true);
            final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            theUnsafe = theUnsafeField.get(null);
        } catch (final Exception ex) {
        }
    }
}

static {
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            getCleanMethodPrivileged();
            return null;
        }
    });
}

private static boolean closeDirectByteBufferPrivileged(
            final ByteBuffer byteBuffer, final LogNode log) {
    try {
        if (cleanMethod == null) {
            if (log != null) {
                log.log("Could not unmap ByteBuffer, cleanMethod == null");
            }
            return false;
        }
        if (PRE_JAVA_9) {
            if (attachmentMethod == null) {
                if (log != null) {
                    log.log("Could not unmap ByteBuffer, attachmentMethod == null");
                }
                return false;
            }
            // Make sure duplicates and slices are not cleaned, since this can result in
            // duplicate attempts to clean the same buffer, which trigger a crash with:
            // "A fatal error has been detected by the Java Runtime Environment:
            // EXCEPTION_ACCESS_VIOLATION"
            // See: https://stackoverflow.com/a/31592947/3950982
            if (attachmentMethod.invoke(byteBuffer) != null) {
                // Buffer is a duplicate or slice
                return false;
            }
            // Invoke ((DirectBuffer) byteBuffer).cleaner().clean()
            final Method cleaner = byteBuffer.getClass().getMethod("cleaner");
            cleaner.setAccessible(true);
            cleanMethod.invoke(cleaner.invoke(byteBuffer));
            return true;
        } else {
            if (theUnsafe == null) {
                if (log != null) {
                    log.log("Could not unmap ByteBuffer, theUnsafe == null");
                }
                return false;
            }
            // In JDK9+, calling the above code gives a reflection warning on stderr,
            // need to call Unsafe.theUnsafe.invokeCleaner(byteBuffer) , which makes
            // the same call, but does not print the reflection warning.
            try {
                cleanMethod.invoke(theUnsafe, byteBuffer);
                return true;
            } catch (final IllegalArgumentException e) {
                // Buffer is a duplicate or slice
                return false;
            }
        }
    } catch (final Exception e) {
        if (log != null) {
            log.log("Could not unmap ByteBuffer: " + e);
        }
        return false;
    }
}

/**
 * Close a {@code DirectByteBuffer} -- in particular, will unmap a
 * {@link MappedByteBuffer}.
 * 
 * @param byteBuffer
 *            The {@link ByteBuffer} to close/unmap.
 * @param log
 *            The log.
 * @return True if the byteBuffer was closed/unmapped (or if the ByteBuffer
 *            was null or non-direct).
 */
public static boolean closeDirectByteBuffer(final ByteBuffer byteBuffer,
            final Log log) {
    if (byteBuffer != null && byteBuffer.isDirect()) {
        return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override
            public Boolean run() {
                return closeDirectByteBufferPrivileged(byteBuffer, log);
            }
        });
    } else {
        // Nothing to unmap
        return false;
    }
}

requires jdk.unsupportedJDK 9+ のモジュラー ランタイムでは、モジュール記述子にを追加する必要があることに注意してください( を使用するために必要ですUnsafe)。

jarにはRuntimePermission("accessClassInPackage.sun.misc")RuntimePermission("accessClassInPackage.jdk.internal.misc")、およびも必要な場合がありReflectPermission("suppressAccessChecks")ます。

ガベージ コレクション aMappedByteBufferまたは aのより完全なメソッドDirectByteBufferが ClassGraph に実装されています (私は作成者です)。エントリ ポイントはcloseDirectByteBuffer()の最後のメソッドですFileUtils

https://github.com/classgraph/classgraph/blob/latest/src/main/java/nonapi/io/github/classgraph/utils/FileUtils.java#L543

このコードは、リフレクションを使用するように記述されています。これは、使用されている Java API (を含むUnsafe) が近い将来廃止されるためです。

JDK 16+ には追加の問題があることに注意してください。

このコードは、強力なカプセル化を回避するためにNarcissusまたはJVM-Driverライブラリを使用しない限り、JDK 16 以降ではそのままでは機能しません。これは、MappedByteBuffer.clean()がプライベート メソッドであり、JDK 16 が強力なカプセル化を強制するためです。ClassGraph は、実行時にリフレクションを介して Narcissus または JVM ドライバーを呼び出すことにより、カプセル化されたプライベート メソッドへのアクセスを抽象化します。

https://github.com/classgraph/classgraph/blob/latest/src/main/java/nonapi/io/github/classgraph/reflection/ReflectionUtils.java

DirectByteBuffer警告:クリーンアップ (解放) 後ににアクセスしようとすると、VM がクラッシュします。

このバグ レポートの最後のコメントで説明されている、その他のセキュリティに関する考慮事項があります。

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-4724038

于 2019-01-04T22:06:15.257 に答える
2

Java でこのバグを回避するには、次の手順を実行する必要がありました。

    // first open the file for random access
    RandomAccessFile raf = new RandomAccessFile(file, "r");

    // extract a file channel
    FileChannel channel = raf.getChannel();

    // you can memory-map a byte-buffer, but it keeps the file locked
    //ByteBuffer buf =
    //        channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());

    // or, since map locks the file... just read the whole file into memory
    ByteBuffer buf = ByteBuffer.allocate((int)file.length());
    int read = channel.read(buf);

    // .... do something with buf

    channel.force(false);  // doesn't help
    channel.close();       // doesn't help
    channel = null;        // doesn't help
    buf = null;            // doesn't help
    raf.close();           // try to make sure that this thing is closed!!!!!
于 2012-01-19T09:22:15.877 に答える
0

マップされたメモリは、ガベージ コレクタによって解放されるまで使用されます。

FileChannelドキュメントから

いったん確立されたマッピングは、それを作成するために使用されたファイル チャネルに依存しません。特に、チャネルを閉じても、マッピングの有効性には影響しません。

MappedByteBuffer Java docから

マップされたバイト バッファーとそれが表すファイル マッピングは、バッファー自体がガベージ コレクションされるまで有効なままです。

したがって、マップされたバイト バッファーへの参照が残っていないことを確認してから、ガベージ コレクションを要求することをお勧めします。

于 2010-06-04T09:59:06.727 に答える
-2

マップされたファイル バッファー オブジェクトがガベージ コレクションの対象であることが保証されている場合は、バッファーのマップされたメモリを解放するために VM 全体を GC する必要はありません。System.runFinalization() を呼び出すことができます。これにより、マップされたファイル バッファ オブジェクトで finalize() メソッドが呼び出され (アプリ スレッドに参照がない場合)、マップされたメモリが解放されます。

于 2013-08-01T11:44:56.147 に答える
-12

ここでの正しい解決策は、try-with-resources を使用することです。

これにより、チャネルと他のリソースの作成をブロックにスコープすることができます。ブロックが終了すると、チャネルとその他のリソースがなくなり、その後使用できなくなります (それらへの参照がないため)。

メモリ マッピングは、次の GC が実行されるまで元に戻されませんが、少なくともそれへのダングリング リファレンスはありません。

于 2013-08-05T14:56:04.183 に答える