パッケージjava.lang.foreign
Javaランタイム外部のメモリーおよびファンクションへの低レベル・アクセスを提供します。
外部メモリー・アクセス
外部メモリー・アクセスをサポートするために導入される主な抽象化はMemorySegment
PREVIEWで、メモリーの連続したリージョンをモデル化し、Javaヒープの内側または外側に配置します。 通常、メモリー・セグメントは、割り当てるセグメントをバッキングするメモリーのリージョンの存続期間を制御するArena
PREVIEWを使用して割り当てられます。 メモリー・セグメントの内容は、サイズ、オフセットおよび整列制約を問い合せるための基本的な操作を提供するmemory layout
PREVIEWを使用して記述できます。 メモリー・レイアウトでは、「varハンドル」PREVIEWを使用して「アクセス・メモリー・セグメント」に、より抽象的な代替方法も提供されます。これは、「レイアウト・パス」を使用して計算できます。 たとえば、プリミティブ型int
の10個の値を保持するのに十分な大きさのメモリーのオフ・ヒープ・リージョンを割り当て、それを0
から9
の範囲の値で入力するには、次のコードを使用します:
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocate(10 * 4);
for (int i = 0 ; i < 10 ; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
}
int
の10値を格納するのに十分です。 ネイティブ・セグメントは、「限定アリーナ」PREVIEWを使用して割り当てられます。 そのため、ネイティブ・セグメントへのアクセスは、現在のスレッド(アリーナを作成したスレッド)に制限されます。 さらに、アリーナが閉じられると、ネイティブ・セグメントが無効になり、メモリーのバッキング・リージョンが解放されます。 try-with-resources構成の使用に注意してください: このアイドル状態により、「Java言語仕様」の14.20.3項で説明するセマンティクスに従って、ネイティブ・セグメントをバッキングするメモリーのオフ・ヒープ・リージョンがブロックの末尾に解放されます。
メモリー・セグメントは、メモリー・アクセスに関して強力な安全性を保証します。 最初に、メモリー・セグメントへのアクセス時に、アクセス操作で使用されるメモリー・セグメントの境界である「外部」に存在するアドレスにアクセスが発生しないように、アクセス座標が(アクセス時)として検証されます。 この保証「空間安全」と呼ばれます。つまり、メモリー・セグメントへのアクセスは、「Java言語仕様」の15.10.4項で説明されているように、配列アクセスと同様に境界チェックされます。
また、メモリーのリージョンが (i.e. use-after-free)の割り当て解除後にアクセスされないようにするために、セグメントも (アクセス時)を検証して、取得元のアリーナが閉じられていないことを確認します。 ここでは、この保証「時間的安全」を呼び出します。
空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - メモリー・セグメントをバッキングするメモリー・リージョン内の有効なロケーションにアクセス - または失敗。
外部ファンクション・アクセス
外部関数アクセスをサポートするために導入される主な抽象は、SymbolLookup
PREVIEW、FunctionDescriptor
PREVIEWおよびLinker
PREVIEWです。 1つ目はライブラリ内のシンボルの検索に使用されます。2つ目は外部関数のシグネチャのモデル化に使用されますが、3つ目は外部関数をMethodHandle
インスタンスとしてリンクするために使用されるため、クライアントは、C/C++コード(Java Native Interface (JNI)の場合)の中間レイヤーを必要とせずに、外部関数コールをJavaで直接実行できます。
たとえば、Linux/x64プラットフォームでC標準ライブラリ関数strlen
を使用して文字列の長さをコンピュートするには、次のコードを使用できます:
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateUtf8String("Hello");
long len = (long)strlen.invokeExact(cString); // 5
}
strlen
関数を「ルック・アップ」PREVIEWします。「ダウンコール・メソッド・ハンドル」ターゲット指定の関数では、その後「取得済」PREVIEWになります。 リンクを正常に完了するには、strlen
関数のシグネチャを記述したFunctionDescriptor
PREVIEWインスタンスを指定する必要があります。 この情報から、リンカーは、基礎となるプラットフォームのABIで指定されたルールに従って、メソッド・ハンドルの起動(MethodHandle.invokeExact(java.lang.Object...)
を使用して実行)を外部関数コールに変換する一連のステップを一意に決定します。 Arena
PREVIEWクラスには、前述の例に示すように、「変換」PREVIEW Java文字列をゼロで終了するUTF-8文字列に変換するなど、外部コードと対話するための多くの有用なメソッドも用意されています。
制限付きメソッド
このパッケージの一部のメソッドは、「制限付き」とみなされます。 制限付きメソッドは通常、ネイティブの外部キー・データまたはファンクション(あるいはその両方)を、クライアントが直接使用できる最初のクラスのJava API要素にバインドするために使用されます。 たとえば、制限されたメソッドMemorySegment.reinterpret(long)
PREVIEWを使用して、同じアドレスと一時的な境界を持つ新しいセグメントを作成できますが、指定されたサイズで作成できます。 これは、ネイティブ関数と対話するときに取得されるメモリー・セグメントのサイズを変更する場合に役立ちます。
外部データまたはファンクション(あるいはその両方)のバインドは一般的に安全ではないため、正しく実行しないと、バインドされたJava API要素にアクセスしたときにVMがクラッシュしたり、メモリーが破損する可能性があります。 たとえば、MemorySegment.reinterpret(long)
PREVIEWを使用してネイティブ・メモリー・セグメントのサイズを誤って変更すると、JVMがクラッシュしたり、サイズ変更したセグメントにアクセスしようとすると、メモリーがサイレントに破損する可能性があります。 これらの理由から、制限付きメソッドをコールするコードでは、外部データまたは関数(あるいはその両方)がJava APIに正しくバインドされない原因となる可能性のある引数を渡さないことが重要です。
制限されたメソッドの潜在的危険性を考えると、制限されたメソッドが呼び出されるたびに、Javaランタイムは標準エラー・ストリームで警告を発行します。 このような警告は、選択したモジュールに制限されたメソッドへのアクセス権を付与することによって無効にできます。 これは、実装固有のコマンドライン・オプションを使用するか、ModuleLayer.Controller.enableNativeAccess(java.lang.Module)
PREVIEWをコールするなどしてプログラムで実行できます。
このパッケージのすべてのクラスについて、特に指定されていないかぎり、参照型のメソッド引数はnullにできず、null引数はNullPointerException
を列挙します。 このファクトは、このAPIのメソッドについて個別には記載されていません。
- APIのノート:
- 通常、メモリー・モデルでは、6.6や10.4などに示されるこれらのセグメントはメモリーのオフ・ヒープ・リージョンによってバックアップされるため、ネイティブ・メモリー・セグメントへのアクセス時には適用されません。
- 実装上のノート:
- リファレンス実装では、コマンドライン・オプション
--enable-native-access=M1,M2, ... Mn
を使用して、制限付きメソッドへのアクセスを特定のモジュールに付与できます。M1
,M2
,... Mn
はモジュール名(名前のないモジュールの場合、特殊な値ALL-UNNAMED
を使用できます)です。 このオプションが指定されている場合、制限付きメソッドへのアクセスは、そのオプションでリストされたモジュールにのみ付与されます。 このオプションを指定しない場合、すべてのモジュールで制限付きメソッドへのアクセスが有効になりますが、制限付きメソッドへのアクセスでは実行時警告が発生します。 - 外部仕様
-
インタフェースクラス説明Preview.メモリーの一部のリージョンのアドレスをモデル化するために使用される値のレイアウト。Preview.アリーナは、ネイティブ・メモリー・セグメントのライフサイクルを制御し、柔軟な割当てとタイムリな割当て解除の両方を提供します。Preview.関数記述子は、外部関数のシグネチャをモデル化します。Preview.複数の異種「メンバー・レイアウト」の集計である複合レイアウト。Preview.リンカーは、Javaコードから外部関数にアクセスでき、外部関数からJavaコードにアクセスできます。Preview.リンカー・オプションは、リンケージ・リクエストに追加のパラメータを指定するために使用されます。Preview.メモリー・レイアウトは、メモリー・セグメントの内容を記述します。Preview.「レイアウト・パス」内の要素。Preview.メモリー・セグメントは、メモリーの連続したリージョンへのアクセスを提供します。Preview.スコープは、関連付けられているすべてのメモリー・セグメントのlifetimeをモデル化します。Preview.パディング・レイアウト。Preview.「メモリー・セグメント」PREVIEWの割当てに使用できるオブジェクト。Preview.特定の「要素レイアウト」の均質な繰返しを示す複合レイアウト。Preview.メンバー・レイアウトが次々にレイアウトされるグループ・レイアウト。Preview.「シンボル・ルックアップ」は、1つ以上のライブラリ内のシンボルのアドレスを取得します。Preview.メンバー・レイアウトが同じ開始オフセットでレイアウトされるグループ・レイアウト。Preview.基本データ型の値をモデル化するレイアウト。Preview.キャリアが
boolean.class
の値レイアウト。Preview.キャリアがbyte.class
の値レイアウト。Preview.キャリアがchar.class
の値レイアウト。Preview.キャリアがdouble.class
の値レイアウト。Preview.キャリアがfloat.class
の値レイアウト。Preview.キャリアがint.class
の値レイアウト。Preview.キャリアがlong.class
の値レイアウト。Preview.キャリアがshort.class
の値レイアウト。