メモリー・セグメントとアリーナ

MemorySegmentインタフェースを介して外部関数およびメモリー(FFM) APIでオフヒープまたはオンヒープ・メモリーにアクセスできます。各メモリー・セグメントは、メモリーの連続領域に関連付けられて(支えられて)います。メモリー・セグメントには、次の2種類があります。

  • ヒープ・セグメント: これは、オンヒープ領域であるJavaヒープ内のメモリー領域によって支えられたメモリー・セグメントです。
  • ネイティブ・セグメント: これは、オフヒープ領域であるJavaヒープ外のメモリー領域によって支えられたメモリー・セグメントです。この章の例では、ネイティブ・セグメントを割り当ててアクセスする方法を示します。

アリーナによって、ネイティブ・メモリー・セグメントのライフサイクルが制御されます。アリーナを作成するには、Arenaインタフェースのいずれかのメソッド(Arena.ofConfined()など)を使用します。メモリー・セグメントを割り当てるには、アリーナを使用します。各アリーナにはスコープがあり、メモリー・セグメントを支えるメモリー領域がいつ割当て解除され、有効でなくなるかを指定します。メモリー・セグメントは、それに関連付けられたスコープがまだ有効であるか、アライブである場合にのみアクセスできます。

この章で説明するほとんどの例では、Arena::ofConfinedで作成された限定アリーナを使用しています。限定アリーナは、制限された決定性の存続期間を提供します。そのスコープは、作成時からクローズ時までアライブです。限定アリーナには所有者スレッドがあります。通常は、作成に使用されたスレッドです。限定アリーナに割り当てられたメモリー・セグメントには、所有者スレッドしかアクセスできません。限定アリーナを所有者スレッド以外のスレッドによって閉じようとすると、例外が発生します。

その他の種類のアリーナがあります。

  • Arena::ofSharedで作成される共有アリーナには、所有者スレッドはありません。共有アリーナに割り当てられたメモリー・セグメントには、複数のスレッドがアクセスできます。また、どのスレッドでも共有アリーナを閉じることができ、閉じる処理は安全かつ原子性に対応することが保証されます。共有アリーナの例については、「スライス・メモリー・セグメント」を参照してください。
  • Arena::ofAutoで作成される自動アリーナ。これは、ガベージ・コレクタによって自動的に管理される領域です。どのスレッドも、自動アリーナによって割り当てられたメモリー・セグメントにアクセスできます。自動アリーナでArena::closeを呼び出すと、UnsupportedOperationExceptionを受け取ります。
  • Arena::globalで作成されるグローバル・アリーナ。どのスレッドも、このアリーナに割り当てられているメモリー・セグメントにアクセスできます。また、これらのメモリー・セグメントのメモリー領域は割当て解除されません。グローバル・アリーナでArena::closeを呼び出すと、UnsupportedOperationExceptionを受け取ります。

次の例では、アリーナを使用してメモリー・セグメントを割り当て、メモリー・セグメントに関連付けられたオフヒープ・メモリーにJava Stringを格納してから、オフヒープ・メモリーの内容を出力します。try-with-resourcesブロックの最後でアリーナが閉じられ、メモリー・セグメントに関連付けられたオフヒープ・メモリーが割当て解除されます。

    String s = "My string";
    try (Arena arena = Arena.ofConfined()) {

        // Allocate off-heap memory
        MemorySegment nativeText = arena.allocateUtf8String(s);

        // Access off-heap memory
        for (int i = 0; i < s.length(); i++ ) {
           System.out.print((char)nativeText.get(ValueLayout.JAVA_BYTE, i)); 
        }            
    } // Off-heap memory is deallocated 

次の各項で、この例について詳しく説明します。

アリーナを使用したメモリー・セグメントの割当て

ArenaインタフェースがSegmentAllocatorインタフェースを実装します。これには、オフヒープ・メモリーを割り当て、Javaデータをそれにコピーするメソッドが含まれています。前の例では、メソッドSegmentAllocator::allocateUtf8Stringを呼び出します。これは、文字列を、UTF-8でエンコードされたNULLで終了するC文字列に変換し、その結果をメモリー・セグメントに格納します。

    String s = "My string";
    try (Arena arena = Arena.ofConfined()) {

        // Allocate off-heap memory
        MemorySegment nativeText = arena.allocateUtf8String(s);
        // ...

より複雑なネイティブ・データ型(C構造体など)の割当てやアクセスの詳細は、「メモリー・レイアウトおよび構造化アクセス」を参照してください。

メモリー・セグメントに関連付けられたオフヒープ・メモリーへのJava文字列の格納

ArenaインタフェースがSegmentAllocatorインタフェースを実装します。これには、オフヒープ・メモリーを割り当て、Javaデータをそれにコピーするメソッドが含まれています。前の例では、メソッドSegmentAllocator::allocateUtf8Stringを呼び出します。これは、文字列を、UTF-8でエンコードされたNULLで終了するC文字列に変換し、その結果をメモリー・セグメントに格納します。

    String s = "My string";
    try (Arena arena = Arena.ofConfined()) {

        // Allocate off-heap memory
        MemorySegment nativeText = arena.allocateUtf8String(s);
        // ...

より複雑なネイティブ・データ型(C構造体など)の割当てやアクセスの詳細は、「メモリー・レイアウトおよび構造化アクセス」を参照してください。

オフヒープ・メモリーの内容の出力

次のコードは、nativeTextという名前のMemorySegmentに格納された文字を出力します:

            // Access off-heap memory
            for (int i = 0; i < s.length(); i++ ) {
               System.out.print((char)nativeText.get(ValueLayout.JAVA_BYTE, i)); 
            }

MemorySegmentインタフェースには、メモリー・セグメントに対する読取りまたは書込みを可能にする様々なアクセス方法が含まれています。各アクセス・メソッドは、引数として値レイアウトを取ります。これは、基本データ型(プリミティブなど)に関連付けられたメモリー・レイアウトをモデル化するものです。値レイアウトでは、サイズ、エンディアンすなわちバイト順序、アクセスされるメモリー部分のビット・アラインメント、およびアクセス操作に使用されるJava型がエンコードされます。

たとえば、MemoryLayout.get(ValueLayout.OfByte,long)は引数としてValueLayout.JAVA_BYTEを取ります。この値レイアウトには次の特性があります:

  • Java byteと同じサイズ
  • バイト・アラインメントが1に設定: つまり、メモリー・レイアウトは8ビットの倍数のメモリー・アドレスに格納されます。
  • バイト順序がByteOrder.nativeOrder()に設定: システムは、複数バイト値のバイトを重要な方から重要でない方(ビッグ・エンディアン)または重要でない方から重要な方(リトル・エンディアン)に並べることができます。

アリーナを閉じる

try-with-resources文などでアリーナが閉じられると、そのアリーナのスコープはアライブではなくなります。そのスコープに関連付けられたすべてのメモリー・セグメントが無効になり、それらを支えるメモリー領域は割当て解除されます。

閉じたアリーナ・スコープに関連付けられたメモリー・セグメントにアクセスしようとすると、次の例に示すIllegalStateExceptionが表示されます:

    String s = "My String";    
    MemorySegment nativeText;
    try (Arena arena = Arena.ofConfined()) {

        // Allocate off-heap memory
        nativeText = arena.allocateUtf8String(s);
    }
    for (int i = 0; i < s.length(); i++ ) {
        // Exception in thread "main" java.lang.IllegalStateException: Already closed
        System.out.print((char)nativeText.get(ValueLayout.JAVA_BYTE, i)); 
    }