モジュール java.base
パッケージ java.lang.foreign

インタフェースMemoryLayout

既知のすべてのサブインタフェース:
AddressLayout, GroupLayout, PaddingLayout, SequenceLayout, StructLayout, UnionLayout, ValueLayout, ValueLayout.OfBoolean, ValueLayout.OfByte, ValueLayout.OfChar, ValueLayout.OfDouble, ValueLayout.OfFloat, ValueLayout.OfInt, ValueLayout.OfLong, ValueLayout.OfShort

public sealed interface MemoryLayout permits SequenceLayout, GroupLayout, PaddingLayout, ValueLayout
メモリー・レイアウトは、メモリー・セグメントの内容を記述します。

レイアウト階層には、指定されたサイズと種類の値を表すために使用される「値レイアウト」と、コンテンツが無視されるメモリー・セグメントの一部を表すために使用される「パディング・レイアウト」の2つのリーフがあります。これらのリーフは主に配置上の理由から存在します。 ValueLayout.JAVA_INTValueLayout.JAVA_FLOAT_UNALIGNEDなどの一般的な値のレイアウト定数は、ValueLayoutクラスで定義されます。 特別な種類の値レイアウト、つまりアドレス・レイアウトを使用して、メモリー・リージョンのアドレスを示す値をモデル化します。

より複雑なレイアウトをより単純なものから導出できます: 「順序レイアウト」は、要素レイアウトの0個以上の出現の同種の繰返しを示します。「グループ・レイアウト」は、0個以上のメンバー・レイアウトの異機種間集計を示します。 グループ・レイアウトには2つの種類があります: 構造体レイアウト。メンバー・レイアウトは順番に配置され、メンバー・レイアウトは同じ開始オフセットに配置される共用体レイアウト

レイアウトは、オプションでnameに関連付けることができます。 レイアウト名は、レイアウト・パスの作成時に参照できます。

Cでは、次の構造体宣言を考慮してください:

typedef struct {
    char kind;
    int value;
} TaggedValues[5];
前述の宣言は、次のようにレイアウト・オブジェクトを使用してモデル化できます。
SequenceLayout TAGGED_VALUES = MemoryLayout.sequenceLayout(5,
    MemoryLayout.structLayout(
        ValueLayout.JAVA_BYTE.withName("kind"),
        MemoryLayout.paddingLayout(3),
        ValueLayout.JAVA_INT.withName("value")
    )
).withName("TaggedValues");

メモリー・レイアウトの特性

すべてのレイアウトにはsize (バイトで表されます)があり、次のように定義されています:
  • 値レイアウトのサイズは、値レイアウトに関連付けられたValueLayout.carrier()によって決まります。 つまり、定数ValueLayout.JAVA_INTは、キャリアintを持ち、サイズは4バイトです。
  • アドレス・レイアウトのサイズはプラットフォームに依存します。 つまり、定数ValueLayout.ADDRESSのサイズは、64ビット・プラットフォームでは8バイトです。
  • パディング・レイアウトのサイズは、常に「紺ストレクション」で明示的に指定されます
  • 要素レイアウトがEで要素数がLのシーケンス・レイアウトのサイズは、EのサイズにLを掛けた値です
  • サイズがS1S2、... SnであるM1M2、... Mnのメンバー・レイアウトを含む構造体レイアウトのサイズは、それぞれS1 + S2 + ... + Snです
  • サイズがS1S2、... Snのメンバー・レイアウトM1M2、... Mnを含む連結レイアウトUのサイズは、max(S1, S2, ... Sn)です。

さらに、すべてのレイアウトには、次のように定義された「自然整列」 (バイトで表されます)があります:

  • パディング・レイアウトの自然な配置は1です
  • サイズがNの値レイアウトの自然な位置合せは、Nです
  • 要素レイアウトがEである順序レイアウトの自然な位置合せは、Eの配置です
  • 整列がそれぞれA1A2、... 「1つの」であるメンバー・レイアウトM1M2、... Mnを含むグループ・レイアウトの自然整列は、max(A1, A2 ... An)です。
レイアウトの配置は、必要に応じて (withByteAlignment(long)を参照してください)で上書きできます。これは、より弱い位置合わせ拘束またはより強い位置合わせ拘束を持つレイアウトを記述するのに便利です。

レイアウト・パス

「レイアウト・パス」は、他のレイアウトにネストされているレイアウトを明確に選択するために使用します。 レイアウト・パスは通常、1つ以上の「パス要素」のシーケンスとして表されます。 (レイアウト・パスのより正式な定義は、belowで提供されています。)。

レイアウト・パスは、次の目的で使用できます:

  • 任意のネストされたレイアウトのoffsetsを取得
  • 選択したレイアウトに対応する値にアクセスするために使用できる「varハンドル」を取得
  • 「選択」は、任意のネストされたレイアウトです。

たとえば、前述のtaggedValues順序レイアウトの場合、first順序要素内のvalueという名前のメンバー・レイアウトのオフセット(バイト)を次のように取得できます:

long valueOffset = TAGGED_VALUES.byteOffset(PathElement.sequenceElement(0),
                                          PathElement.groupElement("value")); // yields 4
同様に、valueという名前のメンバー・レイアウトを次のように選択できます:
MemoryLayout value = TAGGED_VALUES.select(PathElement.sequenceElement(),
                                         PathElement.groupElement("value"));

オープン・パス要素

レイアウト・パス要素の一部(「オープン・パス要素」)は、複数のレイアウトを一度に選択できます。 たとえば、オープン・パス要素MemoryLayout.PathElement.sequenceElement()MemoryLayout.PathElement.sequenceElement(long, long)は、順序レイアウトで未指定の要素を選択します。 1つ以上のオープン・パス要素を含むレイアウト・パスから導出されたvarハンドルには、タイプlongの追加の座標があります。この座標は、クライアントがパス内のオープン要素を「バインド」するために使用できます:
VarHandle valueHandle = TAGGED_VALUES.varHandle(PathElement.sequenceElement(),
                                               PathElement.groupElement("value"));
MemorySegment taggedValues = ...
// reads the "value" field of the third struct in the array (taggedValues[2].value)
int val = (int) valueHandle.get(taggedValues,
        0L,  // base offset
        2L); // sequence index

オープン・パス要素は、「オフセット計算メソッド・ハンドル」の作成にも影響します。 各オープン・パス要素は、取得したメソッド・ハンドルで追加のlongパラメータになります。 このパラメータを使用して、オフセットを計算する順序要素のインデックスを指定できます:

MethodHandle offsetHandle = TAGGED_VALUES.byteOffsetHandle(PathElement.sequenceElement(),
                                                          PathElement.groupElement("kind"));
long offset1 = (long) offsetHandle.invokeExact(0L, 1L); // 0 + (1 * 8) = 8
long offset2 = (long) offsetHandle.invokeExact(0L, 2L); // 0 + (2 * 8) = 16

パス要素を間接参照

「間接参照パス要素」という特別な種類のパス要素を使用すると、メモリー・レイアウトから取得されたvarハンドルがポインタに従うことができます。 次のレイアウトを検討します:
StructLayout RECTANGLE = MemoryLayout.structLayout(
        ValueLayout.ADDRESS.withTargetLayout(
                MemoryLayout.sequenceLayout(4,
                        MemoryLayout.structLayout(
                                ValueLayout.JAVA_INT.withName("x"),
                                ValueLayout.JAVA_INT.withName("y")
                        ).withName("point")
                 )
         ).withName("points")
);
このレイアウトは、矩形を記述する構造体レイアウトです。 単一のフィールドpoints (「ターゲット・レイアウト」が4つの構造体レイアウトのシーケンス・レイアウトであるアドレス・レイアウト)が含まれています。 各構造体レイアウトは、2ディメンション・ポイントを記述し、それぞれxおよびyという名前のペアまたはValueLayout.JAVA_INT座標として定義されます。

間接参照パス要素を使用すると、次のように、矩形内のいずれかの点のy座標にアクセスするvarハンドルを取得できます。

VarHandle rectPointYs = RECTANGLE.varHandle(
        PathElement.groupElement("points"),
        PathElement.dereferenceElement(),
        PathElement.sequenceElement(),
        PathElement.groupElement("y")
);

MemorySegment rect = ...
// dereferences the third point struct in the "points" array, and reads its "y" coordinate (rect.points[2]->y)
int rect_y_2 = (int) rectPointYs.get(rect,
    0L,  // base offset
    2L); // sequence index

レイアウト・パスの整形式

レイアウト・パスは、「初期レイアウト」とも呼ばれるレイアウトC_0に適用されます。 レイアウト・パスの各パス要素は、現在のレイアウトC_i-1を他のレイアウトC_iに更新する関数とみなすことができます。 つまり、レイアウト・パスPのパス要素E1, E2, ... Enごとに、C_i = f_i(C_i-1)をコンピュートします。ここで、f_iは、対象となるパス要素に関連付けられた選択関数で、E_iと示されます。 最後のレイアウトC_iは、「選択したレイアウト」とも呼ばれます。

レイアウト・パスPは、対応する入力レイアウトC_0, C_1, ... C_n-1に対してすべてのパス要素E1, E2, ... Enが整形式になっている場合、初期レイアウトC_0に対して整形式とみなされます。 次のいずれかがtrueの場合、パス要素EはレイアウトLに対して整形式とみなされます:

初期レイアウトC_0に対して整形式ではないレイアウト・パスPを指定しようとすると、IllegalArgumentExceptionになります。

アクセス・モード制限

varHandle(PathElement...)またはValueLayout.varHandle()によって返されるvarハンドルには、選択したレイアウトLから導出される特定のアクセス特性があります。
  • L.carrier()から導出されたキャリア・タイプT
  • L.byteAlignment()から導出された整列制約A
  • L.byteSize()から導出されたアクセス・サイズS
前述の特性に応じて、返されるvarハンドルには特定の「アクセス・モード制限」が含まれる場合があります。 整列制約Aがアクセス・サイズSと互換性がある場合、つまりA >= Sの場合、varハンドルは「連携」であるとします。 整列されたvarハンドルは、次のアクセス・モードをサポートすることが保証されています。
  • すべてのTの読取り/書込みアクセス・モード。 32ビット・プラットフォームでは、longdouble、およびMemorySegmentのアクセス・モードgetおよびsetがサポートされていますが、「Java言語仕様」のセクション17.7に説明されているように、ワード引き裂きにつながる可能性があります。
  • int, long, float, doubleおよびMemorySegmentのアトミック更新アクセス・モード。 (JDKの将来のメジャー・プラットフォーム・リリースでは、現在サポートされていない特定のアクセス・モードの追加タイプがサポートされる場合があります。)
  • intlongおよびMemorySegmentの数値アトミック更新アクセス・モード。 (JDKの将来のメジャー・プラットフォーム・リリースでは、現在サポートされていない特定のアクセス・モードに対して追加の数値型をサポートする場合があります。)
  • intlongおよびMemorySegmentのビット単位のアトミック更新アクセス・モード。 (JDKの将来のメジャー・プラットフォーム・リリースでは、現在サポートされていない特定のアクセス・モードに対して追加の数値型をサポートする場合があります。)
TfloatdoubleまたはMemorySegmentの場合、アトミック更新アクセス・モードはビット単位の表現(それぞれFloat.floatToRawIntBits(float)Double.doubleToRawLongBits(double)およびMemorySegment.address()を参照してください。)を使用して値を比較します。

または、VARハンドルは、その位置合せ制約Aがアクセス・サイズSと互換性がない場合(つまり、A < Sの場合)は「位置なし」です。 整列されていないvarハンドルは、getおよびsetアクセス・モードのみをサポートします。 他のすべてのアクセス・モードでは、UnsupportedOperationExceptionがスローされます。 さらに、サポートされているものの、アクセス・モードgetおよびsetはワード・リングにつながる可能性があります。

可変長配列の操作

順序レイアウトを使用して、サイズが「静的に」であることがわかっている配列の内容を記述する方法を見てきました。 ただし、配列サイズが認識されるのは「動的に」のみです。 このような配列を「可変長配列」と呼びます。 可変長配列には、次の2つの一般的な種類があります。
  • サイズが無関係な変数またはパラメータの値に依存する「トップレベル」可変長配列。
  • 構造体内の可変長配列「ネスト」。そのサイズは、包含構造体内の他のフィールドの値に依存します。
可変長配列はシーケンス・レイアウトを使用して直接モデル化することはできませんが、クライアントは、次の項に示すように、varハンドルを使用して可変長配列の要素への構造化されたアクセスを享受できます。

トップレベルの可変長配列

Cでは、次の構造体宣言を考慮してください:
typedef struct {
    int x;
    int y;
} Point;
前述のコードでは、点は2つの座標(それぞれxおよびy)としてモデル化されます。 次に、Cコードの次のスニペットについて考えてみます。
int size = ...
Point *points = (Point*)malloc(sizeof(Point) * size);
for (int i = 0 ; i < size ; i++) {
   ... points[i].x ...
}
ここでは、ポイント(points)の配列を割り当てます。 特に、配列のサイズは、size変数の値に動的にバインドされます。 ループ内では、配列内のすべての点のx座標がアクセスされます。

このコードをJavaでモデル化するには、Point構造体のレイアウトを次のように定義します。

StructLayout POINT = MemoryLayout.structLayout(
            ValueLayout.JAVA_INT.withName("x"),
            ValueLayout.JAVA_INT.withName("y")
);
ポイントの配列を作成してアクセスする必要があることがわかっているため、可変長配列をモデリングするシーケンス・レイアウトを作成し、シーケンス・レイアウトから必要なアクセスvarハンドルを導出することをお薦めします。 しかし、可変長配列のサイズが不明なため、このアプローチは問題になります。 かわりに、可変長配列の要素への構造化されたアクセスを提供するvarハンドルは、次に示すように、配列要素(例:点レイアウト)を記述するレイアウトから直接取得できます。
VarHandle POINT_ARR_X = POINT.arrayElementVarHandle(PathElement.groupElement("x"));

int size = ...
MemorySegment points = ...
for (int i = 0 ; i < size ; i++) {
    ... POINT_ARR_X.get(segment, 0L, (long)i) ...
}
ここでは、配列内の後続の点の座標xに、arrayElementVarHandle(PathElement...)メソッドを使用して取得されるPOINT_ARR_X varハンドルを使用してアクセスします。 このvarハンドルは、2つのlong座標を備えています: 1つ目はベース・オフセット(0Lに設定)で、2つ目はポイント配列のすべての要素にまたがる論理索引です。

ベース・オフセット座標を使用すると、クライアントは、varハンドル(その例を以下に示します。)に追加のオフセット計算を注入することで、複雑なアクセス操作を表現できます。 ベース・オフセットが定数(前の例のように)クライアントの場合、必要に応じてベース・オフセット・パラメータを削除し、アクセス式を簡素化できます。 これは、MethodHandles.insertCoordinates(VarHandle, int, Object...) varハンドル・アダプタを使用して実現されます。

ネストされた可変長配列

Cでは、次の構造体宣言を考慮してください:
typedef struct {
    int size;
    Point points[];
} Polygon;
前述のコードでは、ポリゴンはサイズ(ポリゴンのエッジ数)およびポイント(ポリゴンの頂点ごとに1つ)の配列としてモデル化されています。 頂点の数は、ポリゴンのエッジの数によって異なります。 そのため、points配列のサイズは、「フレキシブル配列メンバー」 (C99で標準化された機能)を使用して、C宣言でunspecifiedのままになります。

再度、クライアントは、次に示すように、arrayElementVarHandle(PathElement...)メソッドを使用して、ネストされた可変長配列内の要素への構造化アクセスを実行できます。

StructLayout POLYGON = MemoryLayout.structLayout(
            ValueLayout.JAVA_INT.withName("size"),
            MemoryLayout.sequenceLayout(0, POINT).withName("points")
);

VarHandle POLYGON_SIZE = POLYGON.varHandle(0, PathElement.groupElement("size"));
long POINTS_OFFSET = POLYGON.byteOffset(PathElement.groupElement("points"));
POLYGONレイアウトには、サイズzeroのシーケンス・レイアウトが含まれます。 順序レイアウトの要素レイアウトは、前に示したPOINTレイアウトです。 ポリゴン・レイアウトは、ポリゴン・サイズへのアクセスを提供するvarハンドル、および可変長のpoints配列の先頭へのオフセット(POINTS_OFFSET)を取得するために使用されます。

次に、ポリゴン内のすべての点のx座標に次のようにアクセスできます。

MemorySegment polygon = ...
int size = POLYGON_SIZE.get(polygon, 0L);
for (int i = 0 ; i < size ; i++) {
    ... POINT_ARR_X.get(polygon, POINTS_OFFSET, (long)i) ...
}
ここでは、まず、POLYGON_SIZE varハンドルを使用してポリゴン・サイズを取得します。 次に、ループで、ポリゴン内のすべての点のx座標を読み取ります。 これを行うには、POINT_ARR_X varハンドルのオフセット座標にカスタム・オフセット(namely, POINTS_OFFSET)を指定します。 前述のように、ループ・インダクション変数iは、可変長配列のすべての要素に分類するために、POINT_ARR_X varハンドルの索引として渡されます。

実装要件:
このインタフェースの実装は不変、スレッド・セーフ、およびvalue-basedです。
シール済クラス階層グラフ:
MemoryLayoutのシール済クラス階層グラフMemoryLayoutのシール済クラス階層グラフ
導入されたバージョン:
22