オペレーティングシステムは、メモリ領域に対してI / O操作を実行します。これらのメモリ領域は、オペレーティングシステムに関する限り、連続したバイトシーケンスです。I / O操作に参加できるのはバイトバッファだけであることは当然のことです。また、オペレーティングシステムがプロセスのアドレススペース(この場合はJVMプロセス)に直接アクセスして、データを転送することも思い出してください。つまり、I / O動作のターゲットであるメモリ領域は、連続したバイトのシーケンスである必要があります。JVMでは、バイトの配列がメモリに連続して格納されていないか、ガベージコレクタがいつでも移動できます。配列はJavaのオブジェクトであり、そのオブジェクト内にデータを格納する方法は、JVM実装によって異なる場合があります。
このため、ダイレクトバッファの概念が導入されました。ダイレクトバッファは、チャネルおよびネイティブI / Oルーチンとの相互作用を目的としています。ネイティブコードを使用してオペレーティングシステムにメモリ領域を直接排出または埋めるように指示することにより、チャネルが直接またはrawアクセスに使用できるメモリ領域にバイト要素を格納するために最善を尽くします。
通常、ダイレクトバイトバッファはI / O操作に最適です。設計上、JVMで使用できる最も効率的なI / Oメカニズムをサポートします。非ダイレクトバイトバッファはチャネルに渡すことができますが、そうするとパフォーマンスが低下する可能性があります。通常、非ダイレクトバッファーをネイティブI / O操作のターゲットにすることはできません。非ダイレクトByteBufferオブジェクトをチャネルに渡して書き込みを行うと、チャネルは呼び出しごとに暗黙的に次のことを行う場合があります。
- 一時的な直接ByteBufferオブジェクトを作成します。
- 非直接バッファーの内容を一時バッファーにコピーします。
- 一時バッファを使用して低レベルI / O操作を実行します。
- 一時バッファオブジェクトはスコープ外になり、最終的にはガベージコレクションされます。
これにより、すべてのI / Oでバッファのコピーとオブジェクトのチャーンが発生する可能性がありますが、これはまさに避けたいことです。ただし、実装によっては、これが悪いことではない場合があります。ランタイムは、ダイレクトバッファーをキャッシュして再利用するか、その他の巧妙なトリックを実行してスループットを向上させる可能性があります。一度だけ使用するバッファを作成するだけの場合、その違いは重要ではありません。一方、高パフォーマンスのシナリオでバッファを繰り返し使用する場合は、直接バッファを割り当てて再利用することをお勧めします。
直接バッファーはI / Oに最適ですが、非直接バイトバッファーよりも作成にコストがかかる場合があります。ダイレクトバッファが使用するメモリは、標準のJVMヒープをバイパスして、オペレーティングシステム固有のネイティブコードを呼び出すことによって割り当てられます。ホストのオペレーティングシステムとJVMの実装によっては、ダイレクトバッファーのセットアップと破棄は、ヒープに常駐するバッファーよりもかなり高価になる場合があります。ダイレクトバッファのメモリストレージ領域は、標準のJVMヒープの外にあるため、ガベージコレクションの対象にはなりません。
直接バッファと非直接バッファを使用した場合のパフォーマンスのトレードオフは、JVM、オペレーティングシステム、およびコード設計によって大きく異なる可能性があります。ヒープ外にメモリを割り当てることにより、JVMが認識していない追加の力がアプリケーションにかかる可能性があります。追加の可動パーツを使用する場合は、目的の効果が得られることを確認してください。私は古いソフトウェアマキシムをお勧めします。最初にそれを機能させ、次に高速にします。事前の最適化についてあまり心配しないでください。最初に正しさに集中する。JVM実装は、バッファキャッシングまたは他の最適化を実行できる場合があります。これにより、不要な労力をかけずに、必要なパフォーマンスを得ることができます。