モジュール java.base

パッケージjava.lang.foreign


パッケージjava.lang.foreign

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

外部メモリー・アクセス

外部メモリー・アクセスをサポートするために導入される主な抽象化は、Javaヒープの内側または外側に存在するメモリーの連続したリージョンをモデル化するMemorySegmentです。 メモリー・セグメントは、通常、Arenaを使用して割り当てられます。これは、割り当てるセグメントをバッキングするメモリー・リージョンの存続期間を制御します。 メモリー・セグメントの内容は、サイズ、オフセットおよび整列制約を問い合せるための基本的な操作を提供するmemory layoutを使用して記述できます。 メモリー・レイアウトは、レイアウト・パスを使用して計算できる「varハンドル」を使用して「アクセス・メモリー・セグメント」に、より抽象的な代替方法も提供します。

たとえば、プリミティブ型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);
    }
}
このコードは、「ネイティブ」メモリー・セグメント(つまり、オフ・ヒープ・メモリーによってバックアップされるメモリー・セグメント)を作成します。セグメントのサイズは40バイトで、プリミティブ・タイプintの10値を格納するのに十分です。 ネイティブ・セグメントは、「限定アリーナ」を使用して割り当てられます。 そのため、ネイティブ・セグメントへのアクセスは、現在のスレッド(アリーナを作成したスレッド)に制限されます。 さらに、アリーナが閉じられると、ネイティブ・セグメントが無効になり、メモリーのバッキング・リージョンが解放されます。 try-with-resources構成の使用に注意してください: このアイディオムは、「Java言語仕様」14.20.3項で説明するセマンティクスに従って、ネイティブ・セグメントをバッキングするメモリーのオフ・ヒープ・リージョンがブロックの最後に解放されるようにします。

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

また、メモリーのリージョンが (i.e. use-after-free)の割り当て解除後にアクセスされないようにするために、セグメントも (アクセス時)を検証して、取得元のアリーナが閉じられていないことを確認します。 ここでは、この保証「時間的安全」を呼び出します。

空間安全と時間的安全性を組み合せると、各メモリー・アクセス操作が成功することを確認できます - メモリー・セグメントをバッキングするメモリー・リージョン内の有効なロケーションにアクセス - または失敗。

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

外部ファンクション・アクセスをサポートするために導入される主な抽象は、SymbolLookupFunctionDescriptorおよびLinkerです。 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.allocateFrom("Hello");
     long len = (long)strlen.invokeExact(cString); // 5
 }
ここでは、「ネイティブ・リンカー」を取得し、標準Cライブラリのstrlen関数を「探す」するために使用します。この関数をターゲットとする「downcallメソッド・ハンドル」は、その後「取得済」RESTRICTEDになります。 リンクを正常に完了するには、strlenファンクションのシグネチャを記述したFunctionDescriptorインスタンスを指定する必要があります。 この情報から、リンカーは、基礎となるプラットフォームのABIで指定されたルールに従って、メソッド・ハンドルの起動(ここでは、MethodHandle.invokeExact(java.lang.Object...)を使用して実行します。)を外部関数コールに変換する一連のステップを一意に決定します。

Arenaクラスは、前述の例に示すように、「変換中」 Java文字列をゼロで終了するUTF-8文字列に変換するなど、外部コードと対話するための多くの便利なメソッドも提供します。

制限付きメソッド

このパッケージの一部のメソッドは、「制限付き」とみなされます。 制限付きメソッドは通常、ネイティブの外部キー・データまたはファンクション(あるいはその両方)を、クライアントが直接使用できる最初のクラスのJava API要素にバインドするために使用されます。 たとえば、制限付きメソッドMemorySegment.reinterpret(long)RESTRICTEDを使用して、同じアドレスと時間境界を持つ、指定されたサイズの新しいセグメントを作成できます。 これは、ネイティブ関数と対話するときに取得されるメモリー・セグメントのサイズを変更する場合に役立ちます。

外部データまたはファンクション(あるいはその両方)のバインドは一般的に安全ではないため、正しく実行しないと、バインドされたJava API要素にアクセスしたときにVMがクラッシュしたり、メモリーが破損する可能性があります。 たとえば、MemorySegment.reinterpret(long)RESTRICTEDを使用してネイティブ・メモリー・セグメントのサイズを誤って変更すると、JVMがクラッシュしたり、さらに悪い場合は、サイズ変更されたセグメントにアクセスしようとすると、サイレント・メモリーが破損する可能性があります。 これらの理由から、制限付きメソッドをコールするコードでは、外部データまたは関数(あるいはその両方)がJava APIに正しくバインドされない原因となる可能性のある引数を渡さないことが重要です。

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

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

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