パッケージjava.lang.foreign
Javaランタイム外部のメモリーおよびファンクションへの低レベル・アクセスを提供します。
外部メモリー・アクセス
外部メモリー・アクセスをサポートするために導入される主な抽象化は、Javaヒープの内側または外側にある連続メモリー・リージョンをモデル化するMemorySegment
PREVIEWです。 メモリー・セグメントの内容は、memory layout
PREVIEWを使用して記述できます。このPREVIEWは、サイズ、オフセットおよび整列制約を問い合せるための基本的な演算を提供します。 メモリー・レイアウトは、「レイアウト・パス」を使用して計算できる「アクセス変数ハンドル」PREVIEWを使用して、「メモリー・セグメントの参照解除」に対してより抽象的な代替方法も提供します。 たとえば、プリミティブ・タイプint
の10個の値を保持できる大きさのオフ・ヒープ・メモリー・リージョンを割り当て、0
から9
までの値を入力するには、次のコードを使用します:
MemorySegment segment = MemorySegment.allocateNative(10 * 4, MemorySession.openImplicit());
for (int i = 0 ; i < 10 ; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
int
の10値を格納するのに十分です。 ループ内では、メモリー・セグメントの内容を初期化します。「参照解除メソッド」PREVIEWが「値レイアウト」PREVIEWを受け入れ、サイズ、整列制約、バイト順序および間接参照演算に関連付けられたJavaタイプ(この場合、int
)を指定します。 さらに具体的には、メモリー・セグメントを隣接する10のスロット(s[i]
)のセットとして表示する場合、0 <= i < 10
(各スロットのサイズは正確に4バイト)、前述の初期化ロジックによって各スロットがs[i] = i
(0 <= i < 10
)に再度設定されます。
確定的な割当て解除位置
メモリー・セグメントを操作するコードを記述する場合(特に、Javaヒープの外部にあるメモリーによってバックアップされる場合)は、セグメントが使用されなくなったときに、メモリー・セグメントに関連付けられたリソースが解放されることがよく重要となります。 このため、セグメントがunreachableが最適でないことをガベージ・コレクタが判断するのを待機する場合があります。 これらの前提で動作するクライアントは、メモリー・セグメントに関連付けられたメモリーをプログラム的に解放する必要があります。 これを行うには、次に示すように、MemorySession
PREVIEW抽象化を使用します:
try (MemorySession session = MemorySession.openConfined()) {
MemorySegment segment = MemorySegment.allocateNative(10 * 4, session);
for (int i = 0 ; i < 10 ; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
}
安全
このAPIは、メモリー・アクセスに伴って強力な安全性保証を提供します。 まず、メモリー・セグメントの参照を解除するときに、(アクセス時)が検証され、参照解除操作で使用されるメモリー・セグメントの境界が「外部」にあるどのアドレスにもアクセスが発生しないようにします。 この保証「空間安全」と呼ばれます。つまり、メモリー・セグメントへのアクセスは、「Java言語仕様」の15.10.4項で説明されているように、配列アクセスと同様に境界チェックされます。メモリー・セグメントは(前述を参照)を閉じることができるため、セグメントも(アクセス時)を検証して、アクセスされるセグメントに関連付けられたメモリー・セッションが途中でクローズされていないことを確認します。 ここでは、この保証「時間的安全」を呼び出します。 空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - および有効なメモリーのロケーションにアクセス - または失敗。
外部ファンクション・アクセス
外部関数アクセスをサポートするために導入される主な抽象は、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.lookup("strlen").get(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (MemorySession session = MemorySession.openConfined()) {
MemorySegment cString = MemorySegment.allocateNative(5 + 1, session);
cString.setUtf8String(0, "Hello");
long len = (long)strlen.invoke(cString); // 5
}
strlen
記号を「探す」PREVIEWするために使用します。「ダウンコール・メソッド・ハンドル」ターゲット指定の記号は、その後「取得済」PREVIEWです。 リンクを正常に完了するには、strlen
関数のシグネチャを記述したFunctionDescriptor
PREVIEWインスタンスを指定する必要があります。 この情報から、リンカーは、基礎となるプラットフォームのABIで指定されたルールに従って、メソッド・ハンドルの起動(MethodHandle.invoke(java.lang.Object...)
を使用して実行)を外部関数コールに変換する一連のステップを一意に決定します。 MemorySegment
PREVIEWクラスは、前述の例で示したように、Java文字列intoPREVIEWのゼロ終端文字列、UTF-8文字列およびbackPREVIEWを変換するなど、外部コードと対話するための多くの有用なメソッドも提供します。
外部アドレス
メモリー・セグメントがJavaコードから作成されると、セグメント・プロパティ(空間境界,時間境界と境界)はセグメントの作成時に完全に認識されます。 ただし、外部関数と対話する場合、クライアントは多くの場合、rawポインタを受け取ります。 このようなポインタには空間境界がありません。 たとえば、Cタイプchar*
は、特定のサイズの単一のchar
値またはchar
値の配列を参照できます。 また、ポインタには一時的な境界やスレッド制限という概念はありません。
rawポインタは、MemoryAddress
PREVIEWクラスを使用してモデル化されます。 クライアントが外部ファンクション・コールからメモリー・アドレス・インスタンスを受信すると、提供される多数のunsafe 「参照解除メソッド」PREVIEWのいずれかを使用して、直接メモリー間接参照を実行できます:
MemoryAddress addr = ... // obtain address from foreign function call
int x = addr.get(ValueLayout.JAVA_INT, 0);
MemorySession session = ... // initialize a memory session object
MemoryAddress addr = ... // obtain address from foreign function call
MemorySegment segment = MemorySegment.ofAddress(addr, 4, session); // segment is 4 bytes long
int x = segment.get(ValueLayout.JAVA_INT, 0);
Upcalls
また、Linker
PREVIEWインタフェースを使用すると、クライアントは既存のメソッド・ハンドル(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);
}
}
FunctionDescriptor intCompareDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS);
MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.class,
"intCompare",
Linker.upcallType(comparFunction));
FunctionDescriptor
PREVIEWインスタンスを作成する必要があります。今回は、作成する関数ポインタのシグネチャを記述しています。 この記述子を使用して、IntComparator.intCompare
のメソッド・ハンドルの検索に使用できるメソッド・タイプをderivePREVIEWできます。
メソッド・ハンドル・インスタンスがあるため、次のようにLinker
PREVIEWインタフェースを使用して、これを新しい関数ポインタに変換できます:
MemorySession session = ...
Addressable comparFunc = Linker.nativeLinker().upcallStub(
intCompareHandle, intCompareDescriptor, session);
);
FunctionDescriptor
PREVIEWインスタンスは、新しいアップコール・スタブの「作成」PREVIEWに使用されます。関数記述子のレイアウトにより、リンカーは、基礎となるプラットフォームのABIで指定されたルールに従って、外部コードがintCompareHandle
のスタブを呼び出すステップの順序を決定できます。 アップコール・スタブのライフサイクルは、アップコール・スタブの作成時に提供される「メモリー・セッション」PREVIEWに関連付けられます。 この同じセッションは、そのメソッドによって返されるMemorySegment
PREVIEWインスタンスによって使用可能になります。
制限付きメソッド
このパッケージの一部のメソッドは、「制限付き」とみなされます。 制限付きメソッドは通常、ネイティブの外部キー・データまたはファンクション(あるいはその両方)を、クライアントが直接使用できる最初のクラスのJava API要素にバインドするために使用されます。 たとえば、制限付きメソッドMemorySegment.ofAddress(MemoryAddress, long, MemorySession)
PREVIEWを使用して、ネイティブ・アドレスから指定された空間境界を持つ新しいセグメントを作成できます。
外部データまたはファンクション(あるいはその両方)のバインドは一般的に安全ではないため、正しく実行しないと、バインドされたJava API要素にアクセスしたときにVMがクラッシュしたり、メモリーが破損する可能性があります。 たとえば、MemorySegment.ofAddress(MemoryAddress, long, MemorySession)
PREVIEWの場合、指定された空間境界が正しくない場合、そのメソッドによって返されるセグメントのクライアントはVMをクラッシュするか、またはそのセグメントを間接参照しようとしたときにメモリーが破損する可能性があります。 これらの理由から、制限付きメソッドをコールするコードでは、外部データまたは関数(あるいはその両方)がJava APIに正しくバインドされない原因となる可能性のある引数を渡さないことが重要です。
制限されたメソッドへのアクセスは、コマンドライン・オプション--enable-native-access=M1,M2, ... Mn
を使用して制御できます。M1
, M2
, ... Mn
はモジュール名(名前のないモジュールの場合、特殊な値ALL-UNNAMED
を使用できます)です。 このオプションが指定されている場合、制限付きメソッドへのアクセスは、そのオプションでリストされたモジュールにのみ付与されます。 このオプションを指定しない場合、すべてのモジュールで制限付きメソッドへのアクセスが有効になりますが、制限付きメソッドへのアクセスでは実行時警告が発生します。
このパッケージのすべてのクラスについて、特に指定されていないかぎり、参照型のメソッド引数はnullにできず、null引数はNullPointerException
を列挙します。 このファクトは、このAPIのメソッドについて個別には記載されていません。
-
クラス説明Preview.「メモリー・アドレス」PREVIEWに投影できるオブジェクト。Preview.関数記述子は、ゼロ個以上の引数レイアウトとゼロ個以上の戻りレイアウトで構成されます。Preview.複数の「メンバー・レイアウト」を集計する複合レイアウト。Preview.リンカーは、Javaコードから外部関数にアクセスでき、外部関数からJavaコードにアクセスできます。Preview.メモリー・アドレスは、メモリー・ロケーションへの参照をモデル化します。Preview.メモリー・レイアウトを使用して、メモリー・セグメントの内容を記述できます。Preview.「レイアウト・パス」内の要素。Preview.メモリー・セグメントは、連続するメモリー・リージョンをモデル化します。Preview.メモリー・セッションは、1つ以上のリソースのライフサイクルを管理します。Preview.「メモリー・セグメント」PREVIEWの割当てに使用できるオブジェクト。Preview.指定された「要素レイアウト」の繰返しを示す複合レイアウト。Preview.「シンボル・ルックアップ」は、1つ以上のライブラリでシンボルのアドレスを取得するために使用できるオブジェクトです。Preview.変数引数リスト。C
va_list
と機能的に似ています。Preview.値レイアウト。Preview.キャリアがMemoryAddress.class
の値レイアウト。Preview.キャリアがboolean.class
の値レイアウト。Preview.キャリアがbyte.class
の値レイアウト。Preview.キャリアがchar.class
の値レイアウト。Preview.キャリアがdouble.class
の値レイアウト。Preview.キャリアがfloat.class
の値レイアウト。Preview.キャリアがint.class
の値レイアウト。Preview.キャリアがlong.class
の値レイアウト。Preview.キャリアがshort.class
の値レイアウト。