モジュール jdk.incubator.foreign

パッケージ jdk.incubator.foreign


パッケージjdk.incubator.foreign

Javaから直接、低レベルで効率的な外部メモリー/ファンクション・アクセスをサポートするクラス。

外部メモリー・アクセス

外部メモリー・アクセスをサポートするために導入された主要な抽象化は、MemorySegmentおよびMemoryAddressです。 1つ目は、連続するメモリー・リージョンをモデル化するもので、Javaヒープの内部にも外部にも配置できます。後者が、アドレスをモデル化します。 - Javaヒープ(また、特定のセグメントへのオフセットとして表すこともできます)の内部または外部に存在することもできます。 メモリー・セグメントは、MemoryHandlesクラスで定義されたコンビネータ・メソッドを使用して取得できるメモリー・アクセス変数ハンドルのメイン・アクセス座標を表します。一般的な参照解除演算のセットもMemoryAccessクラスによって提供され、これは単純な非構造化アクセスに役立ちます。 最後に、MemoryLayoutクラス階層を使用すると、「メモリー・レイアウト」の説明や、特定のレイアウトのサイズ(バイト単位)の計算、位置合せ要件の取得などの基本演算が可能になります。 メモリー・レイアウトは、レイアウト・パスなどを使用してメモリー・アクセスvarハンドルを生成するための、より抽象的な代替方法も提供します。 たとえば、プリミティブ・タイプintの10個の値を保持できる大きさのオフ・ヒープ・メモリー・リージョンを割り当て、0から9までの値を入力するには、次のコードを使用します:


try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) {
    for (int i = 0 ; i < 10 ; i++) {
       MemoryAccess.setIntAtIndex(segment, i);
    }
}
 
ここでは、「ネイティブ」メモリー・セグメント、つまりオフ・ヒープ・メモリーによってバッキングされるメモリー・セグメントを作成します。セグメントのサイズは40バイトで、プリミティブ型intの10個の値を格納できます。 try-with-resources構成メンバー内にセグメントが作成されます: この場合、「Java言語仕様」14.20.3項で説明されているセマンティクスに従って、セグメントに関連付けられているすべてのメモリー・リソースがブロックの最後に解放されます。 try-with-resourcesブロック内で、MemoryAccess.setIntAtIndex(jdk.incubator.foreign.MemorySegment, long, int)ヘルパー・メソッドを使用してメモリー・セグメントのコンテンツを初期化します; 具体的には、メモリー・セグメントを10個の隣接するスロットのセットとして見る場合、s[i]、ここで、0 <= i < 10の場合、各スロットのサイズは正確に4バイトであり、前述の初期化ロジックは各スロットがs[i] = iに再度設定されます。ここで、0 <= i < 10

確定的な割当て解除位置

メモリー・セグメントを操作するコードを記述する場合、特にJavaヒープの外部にあるメモリーでバックアップする場合、MemorySegment.close()メソッドを明示的に、または暗黙的に、(前述の例で示したように)リソースを構成することによって、セグメントが使用されなくなったときにメモリー・セグメントに関連付けられているリソースが解放されることが重要です。 特定のメモリー・セグメントのクローズは、成功する「原子性」操作です - そして、セグメントに関連付けられている基本的なメモリーが解放されるか、または「失敗」に例外が発生します。

決定論的な割当て解除モデルは、他のAPIに導入された暗黙的な戦略とは大幅に異なるもので、ほとんどの場合ByteBuffer API: その場合、ネイティブ・バイト・バッファが(ByteBuffer.allocateDirect(int)を参照してください)で作成されると、バイト・バッファ参照がunreachableになるまで、基礎となるメモリーは解放されません。 このような暗黙的な割当て解除モデルは非常に便利ですが、 - クライアントは、「閉じる」に直接バッファを覚えている必要はありません。 - また、このようなモデルでは、ダイレクト・バッファに関連付けられたメモリーが実際に解放されていることをクライアント側でハードにできます。

安全

このAPIは、メモリー・アクセスに伴って強力な安全性保証を提供します。 最初に、メモリー・セグメントを間接参照するときに、アクセス座標が検証されて(アクセス時)が検証され、間接参照演算で使用されるメモリー・セグメントの境界に「外部」が存在するアドレスでアクセスされないようにします。 この保証「空間安全」と呼ばれます。つまり、メモリー・セグメントへのアクセスは、「Java言語仕様」15.10.4項で説明されているように、配列アクセスと同様に境界チェックされます。

メモリー・セグメントは(前述を参照)を閉じることができるため、セグメントは、アクセスされているセグメントが早期に閉じられていないことを確認するために(アクセス時)も検証されます。 ここでは、この保証「時間的安全」を呼び出します。 一般的には、複数のスレッドが同じメモリー・セグメントに同時にアクセスまたはクローズ(あるいはその両方)を試みるため、時間的安全性を確保するのは困難な場合があります。 メモリー・アクセスAPIは、メモリー・セグメントに強力なthread-confinement保証を課すことで、この問題に対処: メモリー・セグメントは、作成時に所有者スレッドに関連付けられます。所有者スレッドは、セグメントにアクセスしたり、セグメントを閉じたりできる唯一のスレッドです。

空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - および有効なメモリーのロケーションにアクセス - または失敗。

外部ファンクション・アクセス

外部ファンクション・アクセスをサポートするために導入された主な抽象化は、LibraryLookupおよびCLinkerです。 前者は、外部ライブラリをロードしたり、指定されたライブラリ内のシンボルをルックアップするために使用されます。後者は、ネイティブ・コード(Java Native Interface (JNI)の場合)の中間レイヤーを必要とせずに、クライアントが外部ファンクション・コールをJavaで直接実行できるように、外部ファンクションをMethodHandleインスタンスとしてモデル化できるリンク機能を提供します。

たとえば、Linux x64プラットフォームでC標準ライブラリ関数strlenを使用して文字列の長さをコンピュートするには、次のコードを使用できます:


MethodHandle strlen = CLinker.getInstance().downcallHandle(
        LibraryLookup.ofDefault().lookup("strlen").get(),
        MethodType.methodType(long.class, MemoryAddress.class),
        FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER)
);

try (var cString = CLinker.toCString("Hello")) {
    long len = strlen.invokeExact(cString.address()) // 5
}
 
ここでは、defaultライブラリ・ルック・アップ(LibraryLookup.ofDefault()を参照してください)内のstrlenシンボルをルックアップします。 次に、リンカー・インスタンス(CLinker.getInstance()を参照してください)を取得し、それを使用してstrlenライブラリ・シンボルをターゲットにするメソッド・ハンドルを取得します。 リンクを正常に完了するには、(i)にMethodTypeインスタンスを提供し、生成されるメソッド・ハンドルのタイプを記述し、(ii)にFunctionDescriptorインスタンスを記述してstrlen関数のシグネチャを記述する必要があります。 この情報から、リンカーは、プラットフォームC ABIで指定されたルールに従って、メソッド・ハンドル呼出し(MethodHandle.invokeExact(java.lang.Object...)を使用して実行されます)を外部ファンクション・コールに変換する一連のステップを一意に決定します。 CLinkerクラスには、前述の例に示すように、Java文字列をネイティブ文字列に変換したり、(CLinker.toCString(java.lang.String)およびCLinker.toJavaString(jdk.incubator.foreign.MemorySegment)をそれぞれ参照)を逆変換するなど、ネイティブ・コードと対話するための便利なメソッドも多数用意されています。

外部アドレス

メモリー・セグメントがJavaコードから作成されると、セグメント・プロパティ(空間境界,時間境界と境界)はセグメントの作成時に完全に認識されます。 ただし、ネイティブ・ライブラリと対話する場合、クライアントは多くの場合、rawポインタを受け取ります。このようなポインタには空間境界(example: C言語のchar*型は、指定されたサイズの単一のchar値またはchar値の配列を参照しますか。)、時間境界の概念、およびスレッド定義はありません。

クライアントが外部ファンクション・コールからMemoryAddressインスタンスを受信した場合、そのアドレスが指すメモリーを間接参照するためにMemorySegmentインスタンスの取得が必要になることがあります。 これを行うには、次に説明する3つの方法でクライアントを進めます。

まず、クライアントがすでに所有しているセグメントにメモリー・アドレスが属していることがわかっている場合は、「リベース」演算を実行できます。つまり、クライアントは、特定のセグメントに対するオフセットを確認してから、次のように元のセグメントの間接参照に進むことができます:


MemorySegment segment = MemorySegment.allocateNative(100);
...
MemoryAddress addr = ... //obtain address from native code
int x = MemoryAccess.getIntAtOffset(segment, addr.segmentOffset(segment));
 
次に、クライアントに特定のメモリー・アドレスを含むセグメントがない場合は、MemoryAddress.asSegmentRestricted(long)ファクトリを使用して「不安」を作成できます。 これにより、クライアントは、たとえば、ネイティブ・アドレスを生成した外部ファンクションのドキュメントで使用可能な空間境界に関する追加情報を注入できます。 ネイティブ・アドレスから安全でないセグメントを作成する方法を次に示します:

MemoryAddress addr = ... //obtain address from native code
MemorySegment segment = addr.asSegmentRestricted(4); // segment is 4 bytes long
int x = MemoryAccess.getInt(segment);
 
または、クライアントがフォールバックして「すべて」セグメントと呼ばれるを使用することもできます - つまり、ネイティブ・ヒープ全体をカバーする最初のセグメントです。 このセグメントは、追加のセグメント・インスタンスを作成しなくても間接参照を実行できるように、MemorySegment.ofNativeRestricted()メソッドをコールすることで取得できます:

MemoryAddress addr = ... //obtain address from native code
int x = MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr.toRawLongValue());
 

Upcalls

CLinkerインタフェースでは、Javaコードを他の外部ファンクションに効果的に渡すことができるように、既存のメソッド・ハンドル(Javaメソッドを指す場合があります)をネイティブ・メモリー・セグメント(MemorySegmentを参照してください)に変換することもできます。 たとえば、次のように2つの整数値を比較するメソッドを記述できます:

class IntComparator {
    static int intCompare(MemoryAddress addr1, MemoryAddress addr2) {
        return MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr1.toRawLongValue()) -
               MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr2.toRawLongValue());
    }
}
 
前述のメソッドは、整数値を含む2つのメモリー・アドレスを間接参照し、そのような値の差を返すことで単純な比較を実行します。 次に、前述のstaticメソッドをターゲットとするメソッド・ハンドルを次のように取得できます:

MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.class,
                                                   "intCompare",
                                                   MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class));
 
これでメソッド・ハンドル・インスタンスが作成されたので、次のようにCLinkerインタフェースを使用して新しいネイティブ・メモリー・セグメントにリンクできます:

MemorySegment comparFunc = CLinker.getInstance().upcallStub(
     intCompareHandle,
     FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER)
);
 
前述のように、作成するファンクション・ポインタのシグネチャを記述するFunctionDescriptorインスタンスを指定する必要があります。前述のように、メソッド・ハンドル・タイプと組み合せて、プラットフォームC ABIで指定されたルールに従って外部コードがintCompareHandleをコールできるようにするステップの順序を一意に決定します。

制限付きメソッド

このパッケージの一部のメソッドは、「制限付き」とみなされます。 通常、制限付きメソッドは、ネイティブの外部データまたは関数(あるいはその両方)を、クライアントが直接使用できるファースト・クラスのJava API要素にバインドするために使用されます。 たとえば、制限付きメソッドMemoryAddress.asSegmentRestricted(long)を使用して、ネイティブ・アドレスから指定された空間境界で新しいセグメントを作成できます。

外部データまたはファンクション(あるいはその両方)のバインドは一般的に安全ではないため、正しく実行しないと、バインドされたJava API要素にアクセスしたときにVMがクラッシュしたり、メモリーが破損する可能性があります。 たとえば、MemoryAddress.asSegmentRestricted(long)の場合、指定された空間境界が正しくないと、そのメソッドによって返されたセグメントのクライアントがVMをクラッシュしたり、セグメントを間接参照しようとしたときにメモリーが破損する可能性があります。 これらの理由から、制限付きメソッドをコールするコードでは、外部データまたは関数(あるいはその両方)がJava APIに正しくバインドされない原因となる可能性のある引数を渡さないことが重要です。

制限付きメソッドへのアクセスはデフォルトではdisabledです。制限付きメソッドを有効にするには、JDKプロパティforeign.restricteddeny以外の値に設定する必要があります。 このプロパティに指定できる値は次のとおりです:

  • deny: 各制限付きコールでランタイム例外が発行されます。 これがデフォルト値です。;
  • permit: 制限付きコールを許可
  • warn: like permit、ただし制限付きコールごとに1行の警告を出力
  • debug: permitと似ていますが、指定された制限付き呼び出しに対応するスタックもダンプします。

  • インタフェースのサマリー
    インタフェース
    説明
    addressableのタイプを表します。
    Cリンカーは、Cアプリケーション二項インタフェースの(ABI)呼び出し規則を実装します。
    C va_listをモデル化するインタフェース。
    C va_listの構築に使用されるビルダー・インタフェース。
    ネイティブ・ライブラリ・ルックアップ。
    ライブラリ・ルック・アップ中に取得されるシンボル。
    メモリー・アドレスは、メモリー・ロケーションへの参照をモデル化します。
    メモリー・レイアウトを使用すると、「言語選択なし」方式でメモリー・セグメントのコンテンツを記述できます。
    このクラスのインスタンスは、レイアウト・パスの形成に使用されます。
    メモリー・セグメントは、連続するメモリー・リージョンをモデル化します。
    ネイティブ・スコープは、オフ・ヒープ・メモリーによって支援される、1つ以上の割当てに対して共有の時間境界を提供する抽象化です。
  • クラスのサマリー
    クラス
    説明
    関数記述子は、ゼロ個以上の引数レイアウトとゼロ個以上の戻りレイアウトで構成されます。
    グループ・レイアウトは、複数の「メンバー・レイアウト」を結合するために使用します。
    このクラスは、MappedMemorySegments.force(MemorySegment)MappedMemorySegments.load(MemorySegment)などのマップされたメモリー・セグメントを操作する機能を提供します。
    このクラスは、様々な方法でメモリー・セグメントを間接参照するために使用できる、すぐに使用可能な静的アクセッサを定義します。
    このクラスは、メモリー・アクセス変数ハンドルを構成および結合するためのいくつかのファクトリ・メソッドを定義します。
    このクラスは、便利なレイアウト定数を定義します。
    順序レイアウト。
    値レイアウト。
  • 列挙クラスの概要
    列挙クラス
    説明
    C型のタイプです。