パッケージjdk.incubator.foreign
Javaから直接、低レベルで効率的な外部メモリー/ファンクション・アクセスをサポートするクラス。
外部メモリー・アクセス
外部メモリー・アクセスをサポートするために導入された主要な抽象化は、MemorySegment
およびMemoryAddress
です。 1つ目は、連続するメモリー・リージョンをモデル化するもので、Javaヒープの内部にも外部にも配置できます。後者が、アドレスをモデル化します。 - Javaヒープ(また、特定のセグメントへのオフセットとして表すこともできます)の内部または外部に存在することもできます。 メモリー・セグメントは、MemoryHandles
クラスで定義されたコンビネータ・メソッドを使用して取得できるメモリー・アクセス変数ハンドルのメイン・アクセス座標を表します。一般的な参照解除演算のセットもMemoryAccess
クラスによって提供され、これは単純な非構造化アクセスに役立ちます。 最後に、MemoryLayout
クラス階層を使用すると、「メモリー・レイアウト」の説明や、特定のレイアウトのサイズ(バイト単位)の計算、位置合せ要件の取得などの基本演算が可能になります。 メモリー・レイアウトは、「レイアウト・パス」などを使用してメモリー・アクセスvarハンドルを生成するための、より抽象的な代替方法も提供します。 たとえば、プリミティブ・タイプint
の10個の値を保持できる大きさのオフ・ヒープ・メモリー・リージョンを割り当て、0
から9
までの値を入力するには、次のコードを使用します:
MemorySegment segment = MemorySegment.allocateNative(10 * 4, ResourceScope.newImplicitScope());
for (int i = 0 ; i < 10 ; i++) {
MemoryAccess.setIntAtIndex(segment, i, 42);
}
ここでは、「ネイティブ」メモリー・セグメント、つまりオフ・ヒープ・メモリーによってバッキングされるメモリー・セグメントを作成します。セグメントのサイズは40バイトで、プリミティブ型int
の10個の値を格納できます。 ループ内では、MemoryAccess.setIntAtIndex(jdk.incubator.foreign.MemorySegment, long, 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++) {
MemoryAccess.setIntAtIndex(segment, i, 42);
}
}
この例は、前述の例とほぼ同じです。今回は、最初に「リソース・スコープ」というものを作成します。これは、直後に作成されたセグメントのライフサイクルを「バインド」するために使用します。 try-with-resources構成の使用に注意してください: この場合、「Java言語仕様」の14.20.3項で説明されているセマンティクスに従って、セグメントに関連付けられているすべてのメモリー・リソースがブロックの最後に解放されます。
安全
このAPIは、メモリー・アクセスに伴って強力な安全性保証を提供します。 最初に、メモリー・セグメントを間接参照するときに、アクセス座標が検証されて(アクセス時)が検証され、間接参照演算で使用されるメモリー・セグメントの境界に「外部」が存在するアドレスでアクセスされないようにします。 この保証「空間安全」と呼ばれます。つまり、メモリー・セグメントへのアクセスは、「Java言語仕様」の15.10.4項で説明されているように、配列アクセスと同様に境界チェックされます。メモリー・セグメントは(前述を参照)を閉じることができるため、セグメントは(アクセス時)も検証され、アクセスされるセグメントに関連付けられたリソース・スコープが途中でクローズされていないことを確認します。 ここでは、この保証「時間的安全」を呼び出します。 空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - および有効なメモリーのロケーションにアクセス - または失敗。
外部ファンクション・アクセス
外部ファンクション・アクセスをサポートするために導入された主な抽象化は、SymbolLookup
およびCLinker
です。 前者はネイティブ・ライブラリ内の記号をルックアップするために使用されます。後者は、外部関数をMethodHandle
インスタンスとしてモデル化できるリンク機能を提供するため、クライアントはネイティブ・コード(Java Native Interface (JNI)の場合)の中間レイヤーを必要とせずに、Javaで外部関数コールを直接実行できます。
たとえば、Linux x64プラットフォームでC標準ライブラリ関数strlen
を使用して文字列の長さをコンピュートするには、次のコードを使用できます:
MethodHandle strlen = CLinker.getInstance().downcallHandle(
CLinker.systemLookup().lookup("strlen").get(),
MethodType.methodType(long.class, MemoryAddress.class),
FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER)
);
try (var scope = ResourceScope.newConfinedScope()) {
var cString = CLinker.toCString("Hello", scope);
long len = (long)strlen.invokeExact(cString.address()); // 5
}
ここでは、「システム・ルックアップ」のstrlen
シンボルをルックアップします。 次に、リンカー・インスタンス(CLinker.getInstance()
を参照してください)を取得し、それを使用してstrlen
ライブラリ・シンボルをターゲットにするメソッド・ハンドルを取得します。 リンクを正常に完了するには、(i)にMethodType
インスタンスを提供し、生成されるメソッド・ハンドルのタイプを記述し、(ii)にFunctionDescriptor
インスタンスを記述してstrlen
関数のシグネチャを記述する必要があります。 この情報から、リンカーは、プラットフォームC ABIで指定されたルールに従って、メソッド・ハンドル呼出し(MethodHandle.invokeExact(java.lang.Object...)
を使用して実行されます)を外部ファンクション・コールに変換する一連のステップを一意に決定します。 CLinker
クラスには、前述の例に示すように、Java文字列をネイティブ文字列に変換したり、(CLinker.toCString(java.lang.String, ResourceScope)
およびCLinker.toJavaString(jdk.incubator.foreign.MemorySegment)
をそれぞれ参照)を逆変換するなど、ネイティブ・コードと対話するための便利なメソッドも多数用意されています。
外部アドレス
メモリー・セグメントがJavaコードから作成されると、セグメント・プロパティ(空間境界,時間境界と境界)はセグメントの作成時に完全に認識されます。 ただし、ネイティブ・ライブラリと対話する場合、クライアントは多くの場合、rawポインタを受け取ります。このようなポインタには空間境界(example: C言語のchar*
型は、指定されたサイズの単一のchar
値またはchar
値の配列を参照しますか。)、時間境界の概念、およびスレッド定義はありません。
クライアントが外部ファンクション・コールからMemoryAddress
インスタンスを受信した場合、そのアドレスが指すメモリーを間接参照するためにMemorySegment
インスタンスの取得が必要になることがあります。 これを行うには、次に説明する3つの方法でクライアントを進めます。
まず、クライアントがすでに所有しているセグメントにメモリー・アドレスが属していることがわかっている場合は、「リベース」演算を実行できます。つまり、クライアントは、特定のセグメントに対するオフセットを確認してから、次のように元のセグメントの間接参照に進むことができます:
MemorySegment segment = MemorySegment.allocateNative(100, scope);
...
MemoryAddress addr = ... //obtain address from native code
int x = MemoryAccess.getIntAtOffset(segment, addr.segmentOffset(segment));
次に、クライアントに特定のメモリー・アドレスを含むセグメントがない場合は、MemoryAddress.asSegment(long, ResourceScope)
ファクトリを使用して「不安」を作成できます。 これにより、クライアントは、たとえば、ネイティブ・アドレスを生成した外部ファンクションのドキュメントで使用可能な空間境界に関する追加情報を注入できます。 ネイティブ・アドレスから安全でないセグメントを作成する方法を次に示します:
ResourceScope scope = ... // initialize a resource scope object
MemoryAddress addr = ... //obtain address from native code
MemorySegment segment = addr.asSegment(4, scope); // segment is 4 bytes long
int x = MemoryAccess.getInt(segment);
または、クライアントがフォールバックして「すべて」セグメントと呼ばれるを使用することもできます - つまり、ネイティブ・ヒープ全体をカバーする最初のセグメントです。 このセグメントは、追加のセグメント・インスタンスを作成しなくても間接参照を実行できるように、MemorySegment.globalNativeSegment()
メソッドをコールすることで取得できます:
MemoryAddress addr = ... //obtain address from native code
int x = MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr.toRawLongValue());
Upcalls
CLinker
インタフェースでは、既存のメソッド・ハンドル(Javaメソッドを指す場合があります)をネイティブ・メモリー・アドレス(MemoryAddress
を参照してください)に変換できるため、Javaコードを他の外部関数に効率的に渡すことができます。 たとえば、次のように2つの整数値を比較するメソッドを記述できます:
class IntComparator {
static int intCompare(MemoryAddress addr1, MemoryAddress addr2) {
return MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr1.toRawLongValue()) -
MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr2.toRawLongValue());
}
}
前述のメソッドは、整数値を含む2つのメモリー・アドレスを間接参照し、そのような値の差を返すことで単純な比較を実行します。 次に、前述のstaticメソッドをターゲットとするメソッド・ハンドルを次のように取得できます:
MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.class,
"intCompare",
MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class));
メソッド・ハンドル・インスタンスがあるため、次のようにCLinker
インタフェースを使用して、それを新しいネイティブ・メモリー・アドレスにリンクできます:
ResourceScope scope = ...
MemoryAddress comparFunc = CLinker.getInstance().upcallStub(
intCompareHandle,
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER),
scope
);
前述のように、作成するファンクション・ポインタのシグネチャを記述するFunctionDescriptor
インスタンスを指定する必要があります。前述のように、メソッド・ハンドル・タイプと組み合せて、プラットフォームC ABIで指定されたルールに従って外部コードがintCompareHandle
をコールできるようにするステップの順序を一意に決定します。 CLinker.upcallStub(java.lang.invoke.MethodHandle, jdk.incubator.foreign.FunctionDescriptor, jdk.incubator.foreign.ResourceScope)
によって返されるメモリー・アドレスのライフサイクルは、そのメソッドに渡される「リソース・スコープ」パラメータに関連付けられています。
制限付きメソッド
このパッケージの一部のメソッドは、「制限付き」とみなされます。 制限付きメソッドは通常、ネイティブの外部キー・データまたはファンクション(あるいはその両方)を、クライアントが直接使用できる最初のクラスのJava API要素にバインドするために使用されます。 たとえば、制限付きメソッドMemoryAddress.asSegment(long, ResourceScope)
を使用して、ネイティブ・アドレスから指定された空間境界で新しいセグメントを作成できます。
外部データまたはファンクション(あるいはその両方)のバインドは一般的に安全ではないため、正しく実行しないと、バインドされたJava API要素にアクセスしたときにVMがクラッシュしたり、メモリーが破損する可能性があります。 たとえば、MemoryAddress.asSegment(long, ResourceScope)
の場合、指定された空間境界が正しくないと、そのメソッドによって返されたセグメントのクライアントがVMをクラッシュしたり、セグメントを間接参照しようとしたときにメモリーが破損する可能性があります。 これらの理由から、制限付きメソッドをコールするコードでは、外部データまたは関数(あるいはその両方)がJava APIに正しくバインドされない原因となる可能性のある引数を渡さないことが重要です。
制限されたメソッドへのアクセスは、デフォルトでdisabledです。制限されたメソッドを有効にするには、コマンドライン・オプション--enable-native-access
でコール元モジュールの名前を指定する必要があります。
-
クラス説明addressableのタイプを表します。Cリンカーは、Cアプリケーション二項インタフェースの(ABI)呼び出し規則を実装します。C型のタイプです。C
va_list
をモデル化するインタフェース。Cva_list
の構築に使用されるビルダー・インタフェース。関数記述子は、ゼロ個以上の引数レイアウトとゼロ個以上の戻りレイアウトで構成されます。グループ・レイアウトは、複数の「メンバー・レイアウト」を結合するために使用します。このクラスは、様々な方法でメモリー・セグメントを間接参照するために使用できる、すぐに使用可能な静的アクセッサを定義します。メモリー・アドレスは、メモリー・ロケーションへの参照をモデル化します。このクラスは、メモリー・アクセス変数ハンドルを構成および結合するためのいくつかのファクトリ・メソッドを定義します。メモリー・レイアウトを使用すると、「言語選択なし」方式でメモリー・セグメントのコンテンツを記述できます。このクラスのインスタンスは、「レイアウト・パス」の形成に使用されます。このクラスは、便利なレイアウト定数を定義します。メモリー・セグメントは、連続するメモリー・リージョンをモデル化します。リソース・スコープは、1つ以上のリソースのライフサイクルを管理します。抽象化はリソース・スコープ・ハンドルをモデリングします。このインタフェースは、ロケータをモデル化します。順序レイアウト。シンボル・ルックアップ。値レイアウト。