support原文はこちら。Oracle).http://cr.openjdk.java.net/~mcimadamore/panama/foreign-memaccess.htmlネイティブとの相互運用のストーリーの重要な部分は、効率的な方法でオフヒープメモリにアクセスする能力に手がかりがあります。APIを使用しています。14、15のインキュベーションAPIとして利用可能になっており、Panamaの相互運用ストーリーの中で最も成熟した部分です。Incubator)https://openjdk.java.net/jeps/383Segmentsメモリ・セグメントは、Javaヒープ上またはJavaヒープ外に位置するメモリ領域をモデル化するために利用可能な抽象化です。セグメントは、ネイティブ・メモリから割り当てられたり(mallocなど)、既存のメモリ・ソースにラップされたり(Java配列やByteBufferなど)できます。メモリ・セグメントは、メモリのデリファレンスを安全にする強力な保証を提供します。具体的には、メモリ・セグメントは以下を提供します。空間的な境界:セグメントにはベースアドレスとサイズがあり、境界外のセグメントへのアクセスは禁止されています。時間的な境界:セグメントは状態を持っています。セグメントを利用し、背後のメモリが不要になったときにセグメントをクローズできます(これは、メモリの解放を引き起こす可能性があることに注意してください)。スレッド制限:セグメントはそのセグメントを作成したスレッドが所有するメモリ領域上のビューです。このスレッドの外部でセグメントのデリファレンスやクローズは禁止されています(これは、マルチスレッドのシナリオでアクセスとクローズの競合を避けるために非常に重要です)。以下のスニペットでは100バイトのオフヒープを割り当てています。文の中で使用できます。これにより、セグメントが不要になった時点でメモリをリリースすることを保証できます。メモリ・セグメントはスライスをサポートしています。セグメントを指定すると、元のセグメントよりも空間的な境界を狭くした新しいセグメントを作成できます。バイトの長さを持つスライスを作成します。スライスは親セグメントと同じ時間的境界を持ちます。親セグメントが閉じられると、その親セグメントから派生したすべてのスライスも閉じられます。逆も同様で、スライスを閉じると親セグメント(およびそこから派生した他のすべてのスライス)が閉じられます。viewを作成することで、クライアントがスライスを閉じるのを防ぐことができます。~CLOSE);sharedSliceでcloseを呼び出そうとすると例外が発生します。メモリ・セグメントは様々なアクセスモード(読み書きアクセスを含む)をサポートしており、これを使ってクライアントが利用できる操作のセットを制限できます。handle)を使用するとセグメントに関連付けられたメモリのデリファレンスが可能です。メモリアクセスVarHandleは特殊な種類のVarHandleで、メモリ・セグメントのアクセス座標と、バイト・オフセットを受け取ります。このオフセットは、デリファレンスを行うそのセグメントのベースアドレスからの相対的なオフセットです。メモリアクセスVarHandleは、次のようにして取得できます。ByteOrder.nativeOrder());dereferenceハンドルを作成するには、キャリアタイプを指定しなければなりません。これは、メモリから値を抽出する際などに使用したいタイプで、コンテンツをメモリから読み出したり、メモリに格納したりする際にバイトのスワップを適用するかどうかも指定します。ユーザは追加のアラインメントパラメータ(ここでは示していません)を渡すことができます。これは、メモリのデリファレンスに対し追加の制約を課すのに便利です。クライアントが誤ってアラインメントされた32ビット値へのアクセスを防ぎたい場合などです。10個のint値をセグメントから読み出すために、以下のコードを記述できます。4);}メモリアクセスVarHandleは他のVarHandleと同様、強い型付けがなされており、最大の効率を得るためには、一般にアクセス座標が期待される型に一致することを保証するためにキャストを使う必要があります。ハンドルと同様に)強く型付けされており、最大の効率を得るためには、一般的にアクセス座標が期待される型と一致することを確認するためにキャストを導入する必要があります。キャストしなければなりません。ハンドル操作に強制するためにキャストが必要です。値のバイトサイズである4を掛けて、開始オフセットを手で計算しなければならないことに注意してください。絶対get/putメソッドで行われることと同様です。後ほど、メモリレイアウトがどのように高レベルの構造化アクセスにあたって役立つかを見てみましょう。SafetyメモリアクセスAPIは、メモリのデリファレンス操作に対して基本的な安全性を保証しています。クラッシュや、メモリ・セグメントに関連付けられたメモリの領域外で発生したメモリの破損を絶対に起こしてはならないということです。これを実現するために、すべてのアクセスに対して、空間的および時間的な強力なチェックが行われます。次のコードを考えてみましょう。(IllegalStateException)上記コードは、セグメントの境界外からメモリアクセスしようとしているため実行時例外が発生します。アクセス操作は(境界内の)オフセット8から始めていますが、オフセット11(境界外)で終わっているためです。同様に、すでにクローズ済みのセグメントへのアクセス試行は失敗します。ISE今回は、アクセスはセグメントが暗示する空間的な境界内で発生しますが、アクセス発生時にはセグメントはすでに閉じられているため、アクセス操作は失敗します。これは安全性を保証するために非常に重要です。メモリ・セグメントは決定論的な解放を保証するため、上記のコードは既に解放されたメモリをでリファレンスしようとしてしまう可能性があります。保証に加えて、メモリ・セグメントはスレッドの制限も保証しますが、これについては後述します。コンパイラがホットループの外でほとんどのチェックを行うことができるように設計・実装されていることに注意してください。したがって、メモリアクセス効率はAPIの安全性要件によって悪影響を受けることはありません。Layouts(上記の例のように)バイトオフセットを表現すると、読みにくく、非常に壊れやすいコードになる可能性があります。それは、オフセットを拡張するために使われる定数にメモリレイアウトの不変性が暗黙的に取り込まれるからです。この問題に対処するために、クライアントがプログラムでメモリレイアウトを定義できるメモリレイアウトAPIを追加しました。上記の例で使用されている配列のレイアウトは、以下のコードで表現できます。個の要素の繰り返しです。APIを使用してメモリレイアウトを前もって定義する利点は、レイアウト上でクエリを実行できることです。配列の3番目の要素のオフセットを計算できます。path)と呼ばれるものを使用します。これは、ルートレイアウトから選択したいリーフレイアウト(この場合はシーケンスの3番目のレイアウト要素)までレイアウトをナビゲートする選択式です。レイアウトを使ってメモリアクセスVarHandleを取得することもできるため、上記の例は次のように書き直すことができます。(long)i);}上記の例では、elemHandleが、2個のアクセス座標を取る、int型のvarハンドルです。換言すると、手動オフセット計算はもはや必要ありません。オフセットやストライドは実はレイアウトオブジェクトから導出できるからです。メモリ・レイアウトは構造化アクセスが必要な場合に輝きます。以下のCの宣言を考えてみましょう。TaggedValues[5];このCの宣言を以下のレイアウトを使ってモデル化できます。)).withName("TaggedValues");ここでは、値フィールドのアラインメント要件を満たすために、種類フィールドの後にパディングを挿入する必要があるとします[1]。配列の全要素の値フィールドに手動でオフセット計算を使ってアクセスしなければならないとしたら、コードはすぐにかなり読みにくくなります。バイトであることを覚えておく必要があります。ようなアクセス式が得られます(ここでiは、アクセスが必要な値フィールドを持つ要素のインデックスです)。メモリレイアウトを使うと、以下のようにシーケンス内の値フィールドにアクセスするメモリアクセスVarHandleを簡単に1回で計算できます。ハンドルを使うと、手動オフセット計算は必要ありません。結果として得られるvaluesHandleは、所望の値フィールドをシーケンスから選択するために利用できる追加のlong座標を特長としています。ハンドルが非常にリッチであることにお気づきかもしれません。ハンドルcombinatorsを使用することです。さまざまなcombinatorのメソッドを使用して、単純なメソッドハンドルをより複雑なものに結合できるかを知っています。これらのメソッドを使えば、ターゲットメソッドハンドルに引数を挿入(またはバインド)したり、戻り値をフィルタリングしたり、引数の順序を変えたり、その他多くのことが可能です。ハンドルを扱う際には利用できません。ハンドルコンビネータのセットを追加することでこの問題を解決します。これらのツールを使えば、開発者はvarハンドルの変換を以下のように表現できます。レイアウトを再度利用しています)。ハンドルを導出できました。使って手作業で簡単に作成できます。accessors特に構造化されたアクセスの場合、レイアウトパスとコンビネータAPIを使用して複雑なメモリアクセスVarHandleを作成すると便利です。作成するのは、やりすぎと感じるかもしれません。クラスにstaticなアクセサを用意しており、これを使用すれば、さまざまな方法でセグメントをでリファレンスできます。クライアントがセグメントからint値を読み取りたい場合、以下のメソッドのいずれかを使用できます。I)のアドレスからintの値(4バイト)読み出す(このアクセサは配列アクセスの模倣に便利)。APIを使わなくてもメモリのデリファレンス操作を実現できます。APIのフルパワーが便利かもしれません。Interoperabilityメモリ・セグメントは、既存のメモリソースとの対話に関してはかなり柔軟性があります。次のようなことが可能です。バイトバッファAPIとの双方向の統合のおかげで、ユーザーはメモリ・セグメントの作成が可能で、バイトバッファAPIを使ってのデリファレンスも以下のようにできます。bb.getInt();}メモリ・セグメントからバイトバッファビューを作成する場合、そのバッファは、元のセグメントと同じ時間的制約とスレッド制限が保証されているということは唯一覚えておくべきことです。セグメントがクローズされると、それ以降、(以前に取得した)バイトバッファビューを使ってメモリのデリファレンスをしようとすると、例外をスローして失敗します。segmentsネイティブコードが管理しているかもしれない既存のメモリソースからセグメントを作成する必要がある場合が時としてあります。カスタム・アロケータで管理されているメモリからセグメントを作成したい場合などがこれにあたります。APIでは、JNIメソッドであるNewDirectByteBufferを使って、このような移動が可能です。このネイティブメソッドを使って、後に何もしらないJavaコードに返される新鮮なバイトバッファインスタンスで長いアドレスをラップできます。NewDirectByteBufferhttps://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewDirectByteBufferhttps://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/spec/functions.html#NewDirectByteBufferメモリ・セグメントは同様の機能を提供します。(あるネイティブコールを通じて取得された可能性がある)アドレスが与えられると、与えられた空間的、時間的、および制限された境界で、その周りのセグメントをラップすることが可能になります。クリーンアップアクションを実行します。セグメントがクローズされたときに実行されるクリーンアップアクションを指定することもできます。とある外部で管理されているメモリブロックを指すアドレスを想定します。この場合、安全でないセグメントを以下のように作成できます。null);このコードは安全でないセグメントを指定されたアドレスから作成しています。thread)は現在のスレッドで、セグメントがクローズされた場合にデバッグメッセージを表示するクリーンアップアクションがあります。このように作成されたセグメントはまったく安全ではありません。渡されたアドレスが本当に正しいメモリ位置を指していること、addrで指し示すメモリ領域のサイズが本当に10バイトであること、をランタイムは検証できません。同様に、addrに関連付けられている配下のメモリ領域がMemorySegment::closeの呼び出し前に解放されないことを保証できません。APIにおいて制限された操作です。制限付き操作は、実行中のアプリケーションが読み取り専用のランタイムプロパティ(foreign.restricted=permit)を設定している場合にのみ実行できます。このランタイムプロパティを設定しないで制限付き操作を呼び出そうとすると、ランタイム例外が発生して失敗します。将来的には、制限付き操作へのアクセスをモジュールシステムとより統合したものにすることを計画しています。そのようなモジュールに依存するアプリケーションを実行した場合、ユーザーは制限付きネイティブ操作を実行するためにモジュールに権限を与えなければならないかもしれませんし、ランタイムがアプリケーションのモジュールグラフの構築を拒否します。Confinementセグメントには、空間的および時間的な境界に加えて、スレッド制約があります。)を実行したりすることはできません。スレッド制約は制限的ではありますが、マルチスレッド環境でも最適なメモリアクセス性能を保証するためには非常に重要です。APIが提供する安全性の保証が無効になる可能性があります。APIは、スレッド制約の障壁を緩和するための2つの方法を提供しています。スレッドが明示的なハンドオフ操作を実行することで、セグメントを協調的に共有できます。次のコードを考えてみましょう。confinementとしても知られており、一度に1つのスレッドだけがセグメントにアクセスする必要がある場合、プロデューサー/コンシューマーのユースケースで有用な場合があります。ここで留意すべきは、安全なハンドオフ操作のために、APIは元のセグメントを破棄し(closeが呼び出された場合と同様だが、配下のメモリはリリースしていない状態)、正しいオーナーを持つ新しいセグメントを返す点です。次のスレッドがセグメントにアクセスするまでに、最初のスレッドによる全ての書き込みがメモリにフラッシュされることも実装が確認します。一つは、メモリ・セグメントからSpliteratorインスタンスを取得することで、メモリ・セグメントの内容を(Fork/Joinのようなフレームワークを使って)並列処理できます。メモリ・セグメントの全32ビット値を並列で合計するために、以下のコードを利用できます。}).sum();MemorySegment::spliteratorは、セグメントとシーケンス・レイアウトを受け取り、セグメントを、指定されたシーケンス・レイアウトの要素に対応するチャンクに分割するspliteratorインスタンスを返します。ここでは、100万個の要素を含む配列の要素を合計したいとします。各計算で正確に1つの要素を処理するような並列演算による合計は効率が悪いので、代わりにレイアウトAPIを使用してバルク・シーケンス・レイアウトを導出します。バルク・レイアウトとは、元のレイアウトと同じサイズのシーケンス・レイアウトですが、並列処理をしやすくするために100個の要素のグループに配置します。streamを作成し、セグメントの内容を並列に合計できます。ここでは、セグメントは複数のスレッドから同時にアクセスされますが、アクセスは通常の方法で行われます。元のセグメントからスライスが作成され、スレッドに与えられて計算が行われます。accessランタイムは、スレッドが現在セグメントのスライスにspliteratorを使ってアクセスしているかどうかがわかりますし、そのため、同じセグメントの並列処理が行われている間はセグメントをクローズしないようにすることで安全性を確保できます。confinementやspliteratorベースの共有はすべてのコンテキストで動作するわけではありません。クライアントがある種の危険なセグメントを作成し、それを複数のスレッドで共有したい場合があるかもしれません(スレッドは同期をとって内容に安全にアクセスできるようにする必要があるでしょう)。APIはこのユースケースを直接サポートしていません。安全でないメモリ・セグメント(上記参照)を作成して監禁制限を解除することはできますが、安全かつサポートされている非監禁セグメントを作成する方法はありません。現在このユースケースを実現する解決策を探索中です。現在探索中の主な方策は以下の通りです。Handshakeshttps://openjdk.java.net/jeps/312これらの3つの方法のうち、3番目の方法はメモリ・セグメントから所有権の概念がなくなるために、クライアントの観点からは最も柔軟性が高いと思われますが、実現するには(UnsafeメモリのアクセスAPIへの深い変更が必要ゆえに)最も複雑でもあります。14で検討されました(MemorySegment::acquireメソッドを参照)が、所望のユースケースをサポートするほど強力ではなく、APIが非常に複雑になってしまいました。最後に、問題を完全に解決するわけではありませんが、より粒度の荒い抽象化でメモリ・セグメントに制約を与えることで、有用なフォールバックソリューションがもたらされるかもしれません。一般に、Cの構造体宣言から完全なレイアウトを導出するのは簡単ではありません。ツールが大いに役立つ分野の一つです。コメントをキャンセルコメントを入力してください。以下に詳細を記入するか、アイコンをクリックしてログインしてください。アカウントを使ってコメントしています。アカウントを使ってコメントしています。新しいコメントがついたらメールで通知。Inside現在40人フォロワーがいます。購読管理このバーを折りたたむ%d人のブロガーが「いいね」をつけました。