モジュール java.base

パッケージjava.lang.foreign


パッケージjava.lang.foreign

Javaランタイム外部のメモリーおよびファンクションへの低レベル・アクセスを提供します。

外部メモリー・アクセス

外部メモリー・アクセスをサポートするために導入される主な抽象化はMemorySegmentPREVIEWで、メモリーの連続したリージョンをモデル化し、Javaヒープの内側または外側に配置します。 メモリー・セグメントの内容は、memory layoutPREVIEWを使用して記述できます。このPREVIEWは、サイズ、オフセットおよび整列制約を問い合せるための基本的な演算を提供します。 メモリー・レイアウトは、レイアウト・パスを使用して計算できる「varハンドル」PREVIEWを使用して、「アクセス・メモリー・セグメント」に対してより抽象的な代替方法も提供します。 たとえば、プリミティブ型intの10個の値を保持するのに十分な大きさのメモリーのオフ・ヒープ・リージョンを割り当て、それを0から9の範囲の値で入力するには、次のコードを使用します:

 MemorySegment segment = MemorySegment.allocateNative(10 * 4, SegmentScope.auto());
 for (int i = 0 ; i < 10 ; i++) {
     segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
 }
このコードは、「ネイティブ」メモリー・セグメント(つまり、オフ・ヒープ・メモリーによってバックアップされるメモリー・セグメント)を作成します。セグメントのサイズは40バイトで、プリミティブ・タイプintの10値を格納するのに十分です。 セグメントは「自動スコープ」PREVIEWに関連付けられます。 つまり、セグメントをバッキングするメモリーのオフ・ヒープ・リージョンは、ガベージ・コレクタによって自動的に管理されます。 そのため、ネイティブ・セグメントをバッキングするオフ・ヒープ・メモリーは、セグメントがunreachableになった後、未指定の時点で解放されます。 これは、ByteBuffer.allocateDirect(int)で作成されたダイレクト・バッファの場合と同様です。 割り当てられたネイティブ・セグメントのライフサイクルをより直接管理することもできます(後の項を参照)。

ループ内で、メモリー・セグメントの内容を初期化します。「アクセス・メソッド」PREVIEW「値レイアウト」PREVIEWを受け入れる方法に注意してください。これは、アクセス操作に関連付けられたJavaタイプ(この場合、int)だけでなく、サイズ、整列制約、バイト順序も指定します。 さらに具体的には、メモリー・セグメントを隣接する10のスロット(s[i])のセットとして表示する場合、0 <= i < 10(各スロットのサイズは正確に4バイト)、前述の初期化ロジックによって各スロットがs[i] = i (0 <= i < 10)に再度設定されます。

確定的な割当て解除位置

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

安全レベル

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

アリーナで作成されたメモリー・セグメントは無効な(前述を参照)になる可能性があるため、セグメントは(アクセス時)も検証され、アクセスされるセグメントに関連付けられたスコープがまだ有効であることを確認します。 ここでは、この保証「時間的安全」を呼び出します。 空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - メモリー・セグメントをバッキングするメモリー・リージョン内の有効なロケーションにアクセス - または失敗。

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

外部関数アクセスをサポートするために導入される主な抽象は、SymbolLookupPREVIEWFunctionDescriptorPREVIEWおよびLinkerPREVIEWです。 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").get(),
     FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
 );

 try (Arena arena = Arena.openConfined()) {
     MemorySegment cString = arena.allocateUtf8String("Hello");
     long len = (long)strlen.invoke(cString); // 5
 }
ここでは、「ネイティブ・リンカー」PREVIEWを取得し、標準Cライブラリのstrlen記号を「探す」PREVIEWするために使用します。「ダウンコール・メソッド・ハンドル」ターゲット指定の記号は、その後「取得済」PREVIEWです。 リンクを正常に完了するには、strlen関数のシグネチャを記述したFunctionDescriptorPREVIEWインスタンスを指定する必要があります。 この情報から、リンカーは、基礎となるプラットフォームのABIで指定されたルールに従って、メソッド・ハンドルの起動(MethodHandle.invoke(java.lang.Object...)を使用して実行)を外部関数コールに変換する一連のステップを一意に決定します。 ArenaPREVIEWクラスには、前述の例に示すように、「変換」PREVIEW Java文字列をゼロで終了するUTF-8文字列に変換するなど、外部コードと対話するための多くの有用なメソッドも用意されています。

Upcalls

LinkerPREVIEWインタフェースを使用すると、クライアントは既存のメソッド・ハンドル(Javaメソッドを指す場合があります)をメモリー・セグメントに変換できるため、Javaコードを他の外部関数に効率的に渡すことができます。 たとえば、次のように2つの整数値を比較するメソッドを記述できます:
class IntComparator {
    static int intCompare(MemorySegment addr1, MemorySegment 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.asUnbounded(),
                                                                 ValueLayout.ADDRESS.asUnbounded());
 MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.class,
                                                 "intCompare",
                                                 intCompareDescriptor.toMethodType());
前述のように、FunctionDescriptorPREVIEWインスタンスを作成する必要があります。今回は、作成する関数ポインタのシグネチャを記述しています。 この記述子を使用して、IntComparator.intCompareのメソッド・ハンドルの検索に使用できるメソッド・タイプをderivePREVIEWできます。

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

 SegmentScope scope = ...
 MemorySegment comparFunc = Linker.nativeLinker().upcallStub(
     intCompareHandle, intCompareDescriptor, scope);
 );
前のステップで作成したFunctionDescriptorPREVIEWインスタンスは、新しいアップコール・スタブの「作成」PREVIEWに使用されます。関数記述子のレイアウトにより、リンカーは、基礎となるプラットフォームのABIで指定されたルールに従って、外部コードがintCompareHandleのスタブを呼び出すステップの順序を決定できます。 アップ・コール・スタブのライフサイクルは、アップ・コール・スタブの作成時に提供されるscopePREVIEWに関連付けられます。 この同じスコープは、そのメソッドによって返されるMemorySegmentPREVIEWインスタンスによって使用可能になります。

制限付きメソッド

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

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

制限されたメソッドの潜在的危険性を考えると、制限されたメソッドが呼び出されるたびに、Javaランタイムは標準エラー・ストリームで警告を発行します。 このような警告は、選択したモジュールに制限されたメソッドへのアクセス権を付与することによって無効にできます。 これは、実装固有のコマンドライン・オプションを使用するか、ModuleLayer.Controller.enableNativeAccess(java.lang.Module)PREVIEWをコールするなどしてプログラムで実行できます。

このパッケージのすべてのクラスについて、特に指定されていないかぎり、参照型のメソッド引数はnullにできず、null引数はNullPointerExceptionを列挙します。 このファクトは、このAPIのメソッドについて個別には記載されていません。

APIのノート:
通常、メモリー・モデルでは、6.610.4などに示されるこれらのセグメントはメモリーのオフ・ヒープ・リージョンによってバックアップされるため、ネイティブ・メモリー・セグメントへのアクセス時には適用されません。
実装上のノート:
リファレンス実装では、コマンドライン・オプション--enable-native-access=M1,M2, ... Mnを使用して、制限付きメソッドへのアクセスを特定のモジュールに付与できます。M1, M2, ... Mnはモジュール名(名前のないモジュールの場合、特殊な値ALL-UNNAMEDを使用できます)です。 このオプションが指定されている場合、制限付きメソッドへのアクセスは、そのオプションでリストされたモジュールにのみ付与されます。 このオプションを指定しない場合、すべてのモジュールで制限付きメソッドへのアクセスが有効になりますが、制限付きメソッドへのアクセスでは実行時警告が発生します。