モジュール jdk.incubator.foreign

パッケージjdk.incubator.foreign


パッケージjdk.incubator.foreign

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

外部メモリー・アクセス

外部メモリー・アクセスをサポートするために導入される主な抽象化はMemorySegmentで、連続するメモリー・リージョンがモデル化され、Javaヒープの内側または外側に配置できます。 メモリー・セグメントは、MemoryHandlesクラスで定義されているコンビネータ・メソッドを使用して取得できる、メモリー・アクセスのvarハンドルのメイン・アクセス座標を表します。共通参照およびコピー操作のセットは、単純な非構造化アクセスに役立つMemorySegmentクラスによっても提供されます。 さらに、MemoryLayoutクラス階層では、「メモリー・レイアウト」の記述および特定のレイアウトのバイト単位のサイズの計算、その位置合せ要件の取得などの基本的な操作を実行できます。 また、メモリー・レイアウトは、別のより抽象的な方法を提供し、メモリー・アクセス変数ハンドルを生成します(レイアウト・パスの使用など)。 たとえば、プリミティブ・タイプintの10個の値を保持できる大きさのオフ・ヒープ・メモリー・リージョンを割り当て、0から9までの値を入力するには、次のコードを使用します:

MemorySegment segment = MemorySegment.allocateNative(10 * 4, ResourceScope.newImplicitScope());
for (int i = 0 ; i < 10 ; i++) {
    segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
このコードは、「ネイティブ」メモリー・セグメント(つまり、オフ・ヒープ・メモリーによってバックアップされるメモリー・セグメント)を作成します。セグメントのサイズは40バイトで、プリミティブ・タイプintの10値を格納するのに十分です。 ループ内で、メモリー・セグメントの内容を初期化します。「間接参照メソッド」がサイズ、整列制約、バイト順序および間接参照操作に関連付けられたJavaタイプ(int(この場合は))を指定する「値レイアウト」を受け入れる方法に注意してください。 さらに具体的には、メモリー・セグメントを隣接する10のスロット(s[i])のセットとして表示する場合、0 <= i < 10(各スロットのサイズは正確に4バイト)、前述の初期化ロジックによって各スロットがs[i] = i (0 <= i < 10)に再度設定されます。

確定的な割当て解除位置

メモリー・セグメントを操作するコードを記述する場合(特に、Javaヒープの外部にあるメモリーによってバックアップされる場合)は、セグメントが使用されなくなったときに、メモリー・セグメントに関連付けられたリソースが解放されることがよく重要となります。 このため、セグメントがunreachableが最適でないことをガベージ・コレクタが判断するのを待機する場合があります。 これらの前提で動作するクライアントは、メモリー・セグメントに関連付けられたメモリーをプログラム的に解放する必要があります。 これは、次に示すようにResourceScope抽象化を使用して実行できます:
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
    MemorySegment segment = MemorySegment.allocateNative(10 * 4, scope);
    for (int i = 0 ; i < 10 ; i++) {
        segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
    }
}
この例は、前述の例とほぼ同じです。今回は、最初に「リソース・スコープ」というものを作成します。これは、直後に作成されたセグメントのライフサイクルを「バインド」するために使用します。 try-with-resources構成の使用に注意してください: この場合、「Java言語仕様」14.20.3項で説明されているセマンティクスに従って、セグメントに関連付けられているすべてのメモリー・リソースがブロックの最後に解放されます。

安全

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

メモリー・セグメントは(前述を参照)を閉じることができるため、セグメントは(アクセス時)も検証され、アクセスされるセグメントに関連付けられたリソース・スコープが途中でクローズされていないことを確認します。 ここでは、この保証「時間的安全」を呼び出します。 空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - および有効なメモリーのロケーションにアクセス - または失敗。

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

外部ファンクション・アクセスをサポートするために導入される主要な抽象化は、SymbolLookupMemoryAddressおよびCLinkerです。 1つ目はネイティブ・ライブラリ内部のシンボルを検索するために使用されます。2つ目はネイティブ・アドレス(その上、)のモデル化に使用されますが、3つ目は、外部関数をMethodHandleインスタンスとしてモデリングできるリンク機能を提供し、クライアントがネイティブ・コード(Java Native Interface (JNI)の場合と同様に)の中間レイヤーを必要とせずにJavaで直接外部関数コールを実行できます。

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

var linker = CLinker.systemCLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.lookup("strlen").get(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

try (var scope = ResourceScope.newConfinedScope()) {
    var cString = MemorySegment.allocateNative(5 + 1, scope);
    cString.setUtf8String("Hello");
    long len = (long)strlen.invoke(cString); // 5
}
ここでは、「リンカー・インスタンス」を取得して、標準Cライブラリのstrlen記号を「探す」に使用します。「downcallメソッド・ハンドル」ターゲット指定は、その後「取得」です。 リンクを正常に完了するには、strlen関数のシグネチャを記述したFunctionDescriptorインスタンスを指定する必要があります。 この情報から、リンカーは、プラットフォームC ABIで指定されたルールに従って、メソッド・ハンドル呼出し(MethodHandle.invoke(java.lang.Object...)を使用して実行されます)を外部ファンクション・コールに変換する一連のステップを一意に決定します。 MemorySegmentクラスは、前述の例に示すように、Java文字列の「含まれる」ネイティブ文字列および「後ろ」の変換など、ネイティブ・コードと対話するための多くの有用なメソッドも提供します。

外部アドレス

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

RAWポインタは、MemoryAddressクラスを使用してモデル化されます。 クライアントは、外部ファンクション・コールからメモリー・アドレス・インスタンスを受信すると、提供される多数のunsafe 「間接参照メソッド」のいずれかを使用して、直接メモリー間接参照を実行できます:

MemoryAddress addr = ... //obtain address from native code
int x = addr.get(ValueLayout.JAVA_INT, 0);
または、クライアントはメモリー・セグメント「不安」「作成」できます。 これにより、クライアントは、たとえば、ネイティブ・アドレスを生成した外部ファンクションのドキュメントで使用可能な空間境界に関する追加情報を注入できます。 ネイティブ・アドレスから安全でないセグメントを作成する方法を次に示します:
ResourceScope scope = ... // initialize a resource scope object
MemoryAddress addr = ... //obtain address from native code
MemorySegment segment = MemorySegment.ofAddress(addr, 4, scope); // segment is 4 bytes long
int x = segment.get(ValueLayout.JAVA_INT, 0);

Upcalls

また、CLinkerインタフェースを使用すると、クライアントは既存のメソッド・ハンドル(Javaメソッドを指す場合があります)をメモリー・アドレスに変換できるため、Javaコードを他の外部関数に効果的に渡すことができます。 たとえば、次のように2つの整数値を比較するメソッドを記述できます:
class IntComparator {
    static int intCompare(MemoryAddress addr1, MemoryAddress addr2) {
        return addr1.get(ValueLayout.JAVA_INT, 0) - addr2.get(ValueLayout.JAVA_INT, 0);
    }
}
前述のメソッドは、整数値を含む2つのメモリー・アドレスを間接参照し、そのような値の差を返すことで単純な比較を実行します。 次に、前述のstaticメソッドをターゲットとするメソッド・ハンドルを次のように取得できます:
FunctionDescriptor intCompareDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS);
MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.class,
                                                "intCompare",
                                                CLinker.upcallType(comparFunction));
以前と同様に、作成するファンクション・ポインタのシグネチャを記述したFunctionDescriptorインスタンスを作成する必要があります。 記述子を使用して、IntComparator.intCompareのメソッド・ハンドルを参照するために使用できるメソッド・タイプを「導出」できます。

メソッド・ハンドル・インスタンスがあるため、次のようにCLinkerインタフェースを使用して、新しいファンクション・ポインタに変換できます:

ResourceScope scope = ...
Addressable comparFunc = CLinker.systemCLinker().upcallStub(
    intCompareHandle, intCompareDescriptor, scope);
);
前のステップで作成したFunctionDescriptorインスタンスは、新しいアップ・コール・スタブを「作成」するために使用されます。関数記述子内のレイアウトでは、外部コードがプラットフォームC ABIで指定されたルールに従ってintCompareHandleのスタブをコールできるようにするステップの順序をリンカーが決定できます。 返されるアップ・コール・スタブのライフサイクルは、アップ・コール・スタブの作成時に指定された「リソース・スコープ」に関連付けられます。 この同じスコープは、そのメソッドによって返されたNativeSymbolインスタンスによって使用可能になります。

制限付きメソッド

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

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

制限されたメソッドへのアクセスは、デフォルトでdisabledです。制限されたメソッドを有効にするには、コマンドライン・オプション--enable-native-accessでコール元モジュールの名前を指定する必要があります。

  • クラス
    説明
    addressableのタイプを表します。
    Cリンカーは、Cアプリケーション二項インタフェースの(ABI)呼び出し規則を実装します。
    関数記述子は、ゼロ個以上の引数レイアウトとゼロ個以上の戻りレイアウトで構成されます。
    グループ・レイアウトは、複数の「メンバー・レイアウト」を組み合せるために使用されます。
    メモリー・アドレスは、メモリー・ロケーションへの参照をモデル化します。
    このクラスは、メモリー・アクセス変数ハンドルを構成および結合するためのいくつかのファクトリ・メソッドを定義します。
    メモリー・レイアウトを使用して、メモリー・セグメントの内容を記述できます。
    このクラスのインスタンスは、レイアウト・パスの形成に使用されます。
    メモリー・セグメントは、連続するメモリー・リージョンをモデル化します。
    ネイティブ・シンボルは、ネイティブ・ライブラリ内のロケーション (通常、関数のエントリ・ポイント)への参照をモデル化します。
    リソース・スコープは、1つ以上のリソースのライフサイクルを管理します。
    このインタフェースは、ロケータをモデル化します。
    順序レイアウト。
    シンボル・ルックアップ。
    C va_listと同様に、変数引数リストをモデル化するインタフェース。
    変数引数リストの作成に使用されるビルダー・インタフェース。
    値レイアウト。
    キャリアがMemoryAddress.classの値レイアウト。
    キャリアがboolean.classの値レイアウト。
    キャリアがbyte.classの値レイアウト。
    キャリアがchar.classの値レイアウト。
    キャリアがdouble.classの値レイアウト。
    キャリアがfloat.classの値レイアウト。
    キャリアがint.classの値レイアウト。
    キャリアがlong.classの値レイアウト。
    キャリアがshort.classの値レイアウト。