Module java.base

Interface 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 SequenceLayoutPREVIEW, GroupLayoutPREVIEW, PaddingLayoutPREVIEW, ValueLayoutPREVIEW
MemoryLayout 是 Java 平台的预览 API。
仅当启用预览功能时,程序才能使用 MemoryLayout
预览功能可能会在将来的版本中被移除,或升级为 Java 平台的永久功能。
内存布局描述了内存段的内容。

布局层次结构中有两个叶子节点,值布局预览,用于表示给定大小和类型的值(请参阅和填充布局预览,用于表示应忽略内存段的一部分内容,主要出于对齐原因。一些常见的值布局常量,如ValueLayout.JAVA_INT预览ValueLayout.JAVA_FLOAT_UNALIGNED预览ValueLayout预览类中定义。一种特殊类型的值布局,即地址布局预览,用于模拟表示内存区域地址的值。

更复杂的布局可以从更简单的布局派生出来:序列布局预览表示零个或多个元素布局的同质重复;组布局预览表示零个或多个成员布局的异构聚合。组布局有两种类型:结构布局预览,其中成员布局依次排列,以及联合布局预览,其中成员布局从相同的起始偏移量开始排列。

布局可以选择性地与一个名称关联。在构建布局路径时,可以引用布局名称。

考虑以下在C中的结构声明:

typedef struct {
    char kind;
    int value;
} TaggedValues[5];
可以使用布局对象对上述声明进行建模,如下所示:
SequenceLayout taggedValues = MemoryLayout.sequenceLayout(5,
    MemoryLayout.structLayout(
        ValueLayout.JAVA_BYTE.withName("kind"),
        MemoryLayout.paddingLayout(3),
        ValueLayout.JAVA_INT.withName("value")
    )
).withName("TaggedValues");

内存布局的特性

所有布局都有一个大小(以字节表示),定义如下:
  • 值布局的大小由与值布局关联的ValueLayout.carrier()预览确定。也就是说,常量ValueLayout.JAVA_INT预览的载体是int,大小为4字节;
  • 地址布局的大小取决于平台。也就是说,常量ValueLayout.ADDRESS预览在64位平台上大小为8字节;
  • 填充布局的大小始终在构造时明确提供;
  • 元素布局为E且元素计数为L的序列布局的大小为E的大小乘以L
  • 成员布局为M1M2、... Mn,其大小分别为S1S2、... Sn的结构布局的大小为S1 + S2 + ... + Sn
  • 成员布局为M1M2、... Mn,其大小分别为S1S2、... Sn的联合布局的大小为max(S1, S2, ... Sn)

此外,所有布局都有一个自然对齐(以字节表示),定义如下:

  • 填充布局的自然对齐为1;
  • 大小为N的值布局的自然对齐为N
  • 元素布局为E的序列布局的自然对齐为E的对齐;
  • 成员布局为M1M2、... Mn,其对齐分别为A1A2、... An的组布局的自然对齐为max(A1, A2 ... An)
如果需要,可以覆盖布局的对齐(请参见withByteAlignment(long)),这对于描述具有更弱或更强对齐约束的布局很有用。

布局路径

布局路径用于明确选择嵌套在其他布局中的布局。布局路径通常表示为一个或多个路径元素预览的序列。(有关布局路径的更正式定义,请参见下文)。

布局路径可用于:

  • 获取任意嵌套布局的偏移量
  • 获取一个var handle,可用于访问与所选布局对应的值;
  • 选择一个任意嵌套布局。

例如,给定上面构建的taggedValues序列布局,我们可以获取第一个序列元素中名为value的成员布局的偏移量(以字节为单位),如下所示:

long valueOffset = taggedValues.byteOffset(PathElement.sequenceElement(0),
                                          PathElement.groupElement("value")); // 得到4
类似地,我们可以选择名为value的成员布局,如下所示:
MemoryLayout value = taggedValues.select(PathElement.sequenceElement(),
                                         PathElement.groupElement("value"));

开放路径元素

一些布局路径元素,称为开放路径元素,可以一次选择多个布局。例如,开放路径元素MemoryLayout.PathElement.sequenceElement()预览MemoryLayout.PathElement.sequenceElement(long, long)预览可以选择序列布局中的未指定元素。从包含一个或多个开放路径元素的布局路径派生的var handle具有额外的long类型的坐标,客户端可以使用这些坐标绑定路径中的开放元素:
VarHandle valueHandle = taggedValues.varHandle(PathElement.sequenceElement(),
                                               PathElement.groupElement("value"));
MemorySegment valuesSegment = ...
int val = (int) valueHandle.get(valuesSegment, 2); // 读取数组中第三个结构体的"value"字段

开放路径元素还会影响创建计算偏移量的方法句柄。每个开放路径元素在获得的方法句柄中成为一个额外的long参数。此参数可用于指定要计算偏移量的序列元素的索引:

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

解引用路径元素

一种特殊类型的路径元素,称为解引用路径元素,允许从内存布局获得的var handle跟随指针。考虑以下布局:
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,一个地址布局,其 目标布局预览 是四个结构布局的序列布局。每个结构布局描述一个二维点,并定义为一对 ValueLayout.JAVA_INT预览 坐标,分别命名为 xy

通过解引用路径元素,我们可以获取一个访问矩形中某个点的 y 坐标的变量句柄,如下所示:

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

MemorySegment rect = ...
int rect_y_4 = (int) rectPointYs.get(rect, 2); // rect.points[2]->y

布局路径的良好形式

布局路径应用于布局 C_0,也称为 初始布局。布局路径中的每个路径元素都可以看作是一个函数,它将当前布局 C_i-1 更新为另一个布局 C_i。也就是说,对于布局路径中的每个路径元素 E1, E2, ... En,我们计算 C_i = f_i(C_i-1),其中 f_i 是与考虑的路径元素相关联的选择函数,表示为 E_i。最终布局 C_i 也称为 选定布局

布局路径 P 对于初始布局 C_0 被认为是良好形式的,如果所有其路径元素 E1, E2, ... En 对应于它们的输入布局 C_0, C_1, ... C_n-1 都是良好形式的。对于布局 L,路径元素 E 被认为是对其良好形式的,如果以下任一条件为真:

任何尝试提供对于初始布局 C_0 不良好形式的布局路径 P 将导致 IllegalArgumentException
实现要求:
此接口的实现是不可变的、线程安全的,并且基于值。
封闭类层次图:
MemoryLayout 的封闭类层次图MemoryLayout 的封闭类层次图
自 JDK 版本:
19
  • Method Details

    • byteSize

      long byteSize()
      返回布局大小,以字节为单位。
      返回:
      布局大小,以字节为单位
    • name

      Optional<String> name()
      返回与此布局关联的名称(如果有)。
      返回:
      与此布局关联的名称(如果有)
      参见:
    • withName

      MemoryLayoutPREVIEW withName(String name)
      返回具有与此布局相同特征的内存布局,但具有给定的名称。
      参数:
      name - 布局名称。
      返回:
      具有与此布局相同特征的内存布局,但具有给定的名称
      参见:
    • withoutName

      MemoryLayoutPREVIEW withoutName()
      返回具有与此布局相同特征的内存布局,但没有名称。
      API 注释:
      这对于比较具有不同名称但其他方面相等的两个布局可能很有用。
      返回:
      具有与此布局相同特征的内存布局,但没有名称
      参见:
    • byteAlignment

      long byteAlignment()
      返回与此布局关联的对齐约束,以字节表示。布局对齐定义了一个二的幂 A,即布局的字节对齐,其中 A 是指向此布局的任何指针必须对齐的字节数。因此:
      • A=1 表示未对齐(通常意义上),在数据包中很常见。
      • A=8 表示字对齐(在 LP64 上),A=4 表示 int 对齐,A=2 表示 short 对齐,依此类推。
      • A=64 是 x86/SV ABI(用于 AVX-512 数据)所需的最严格对齐。
      如果未在此布局上设置显式对齐约束(请参见 withByteAlignment(long)),则此方法返回与此布局关联的 自然对齐约束(以字节为单位)。
      返回:
      与此布局关联的对齐约束,以字节表示
    • withByteAlignment

      MemoryLayoutPREVIEW withByteAlignment(long byteAlignment)
      返回具有与此布局相同特征的内存布局,但具有给定的对齐约束(以字节为单位)。
      参数:
      byteAlignment - 布局对齐约束,以字节表示。
      返回:
      具有与此布局相同特征的内存布局,但具有给定的对齐约束(以字节为单位)
      抛出:
      IllegalArgumentException - 如果 byteAlignment 不是二的幂。
    • byteOffset

      default long byteOffset(MemoryLayout.PathElementPREVIEW... elements)
      计算给定布局路径选择的布局的偏移量(以字节为单位),其中路径中的初始布局是此布局。
      参数:
      elements - 布局路径元素。
      返回:
      布局路径中 elements 选择的布局的偏移量,以字节为单位。
      抛出:
      IllegalArgumentException - 如果布局路径对于此布局不是 良构的
      IllegalArgumentException - 如果布局路径包含一个或多个 开放路径元素
      IllegalArgumentException - 如果布局路径包含一个或多个 解引用路径元素
    • byteOffsetHandle

      default MethodHandle byteOffsetHandle(MemoryLayout.PathElementPREVIEW... elements)
      创建一个方法句柄,计算由给定布局路径选择的布局的偏移量(以字节为单位),其中路径中的初始布局是此布局。

      返回的方法句柄具有以下特征:

      • 返回类型为long
      • 它有零个或多个long类型的参数,每个参数对应于提供的布局路径中的每个开放路径元素。这些参数的顺序对应于提供的布局路径中开放路径元素出现的顺序。

      方法句柄返回的最终偏移量计算如下:

      
       偏移量 = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
       
      其中x_1x_2,... x_n是作为long参数提供的动态值,而c_1c_2,... c_m静态偏移常量,s_0s_1,... s_n是从布局路径派生的静态步幅常量。
      API 注意:
      返回的方法句柄可用于计算布局偏移量,类似于byteOffset(PathElement...),但更灵活,因为在调用方法句柄时可以指定一些索引。
      参数:
      elements - 布局路径元素。
      返回:
      一个方法句柄,计算由给定布局路径选择的布局的偏移量(以字节为单位)。
      抛出:
      IllegalArgumentException - 如果布局路径对于此布局不是良构的
      IllegalArgumentException - 如果布局路径包含一个或多个解引用路径元素
    • varHandle

      default VarHandle varHandle(MemoryLayout.PathElementPREVIEW... elements)
      创建一个变量句柄,访问由给定布局路径选择的偏移量处的内存段,其中路径中的初始布局是此布局。

      返回的变量句柄具有以下特征:

      • 其类型派生自所选值布局的载体预览
      • 它有零个或多个long类型的访问坐标,每个坐标对应于提供的布局路径中的每个开放路径元素。这些访问坐标的顺序对应于提供的布局路径中开放路径元素出现的顺序。

      返回的变量句柄访问的最终地址可以计算如下:

      
       地址 = base(segment) + 偏移量
       
      其中base(segment)表示一个函数,返回所访问内存段的物理基地址。对于本机段,此函数只返回本机段的地址预览。对于堆段,此函数更复杂,因为堆段的地址是虚拟化的。偏移量值可以用以下形式表示:
      
       偏移量 = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
       
      其中x_1x_2,... x_n是作为long参数提供的动态值,而c_1c_2,... c_m静态偏移常量,s_1s_2,... s_n是从布局路径派生的静态步幅常量。

      此外,提供的动态值必须符合从布局路径派生的边界,即0 <= x_i < b_i,其中1 <= i <= n,否则将抛出IndexOutOfBoundsException

      基地址必须根据根布局(此布局)的对齐约束对齐。请注意,这可能比所选值布局的对齐约束更严格(但不会更宽松)。

      可以链接多个路径,使用解引用路径元素。解引用路径元素构造一个新的本机内存段,其基地址是通过访问立即在解引用路径元素之前的布局路径元素确定的偏移量的地址值。换句话说,如果布局路径包含一个或多个解引用路径元素,则返回的变量句柄访问的最终地址可以计算如下:

      
       地址_1 = base(segment) + 偏移量_1
       地址_2 = base(segment_1) + 偏移量_2
       ...
       地址_k = base(segment_k-1) + 偏移量_k
       
      其中k是布局路径中解引用路径元素的数量,segment是输入段,segment_1,... segment_k-1是通过解引用与给定解引用路径元素关联的地址获得的段(例如,segment_1是一个基地址为address_1的本机段),偏移量_1偏移量_2,... 偏移量_k是通过评估给定解引用操作后的路径元素计算的偏移量(这些偏移量是使用上述计算获得的)。在这些更复杂的访问操作中,立即在解引用操作之前执行的所有内存访问(例如,地址为address_1address_2,...,address_k-1处的访问)都使用VarHandle.AccessMode.GET访问模式执行。
      API 注意:
      结果变量句柄具有特定的访问模式限制,这些限制适用于所有内存段视图句柄预览
      参数:
      elements - 布局路径元素。
      返回:
      一个变量句柄,访问由给定布局路径选择的偏移量处的内存段。
      抛出:
      IllegalArgumentException - 如果布局路径对于此布局不是良构的
      IllegalArgumentException - 如果由提供的路径选择的布局不是一个值布局预览
      参见:
    • sliceHandle

      default MethodHandle sliceHandle(MemoryLayout.PathElementPREVIEW... elements)
      创建一个方法句柄,给定一个内存段,返回与由给定布局路径选择的布局对应的切片预览,其中路径中的初始布局是此布局。

      返回的方法句柄具有以下特征:

      • 返回类型为MemorySegment
      • 有一个前导参数类型为MemorySegment,对应于要切片的内存段;
      • 有零个或多个long类型的参数,每个参数对应于提供的布局路径中的每个开放路径元素。这些参数的顺序对应于提供的布局路径中开放路径元素出现的顺序。

      返回段的偏移量计算如下:

      long offset = byteOffset(elements);
      long size = select(elements).byteSize();
      MemorySegment slice = segment.asSlice(offset, size);
      

      要切片的段必须根据根布局(此布局)的对齐约束对齐。请注意,这可能比所选值布局的对齐约束更严格(但不会更宽松)。

      API 注意:
      返回的方法句柄可用于获取内存段切片,类似于MemorySegment.asSlice(long, long)预览,但更灵活,因为在调用方法句柄时可以指定一些索引。
      参数:
      elements - 布局路径元素。
      返回:
      一个方法句柄,用于在由给定布局路径选择的偏移量处切片内存段。
      抛出:
      IllegalArgumentException - 如果布局路径对于此布局不是良构的
      IllegalArgumentException - 如果布局路径包含一个或多个解引用路径元素
    • select

      default MemoryLayoutPREVIEW select(MemoryLayout.PathElementPREVIEW... elements)
      返回从提供的路径选择的布局,其中路径中的初始布局是此布局。
      参数:
      elements - 布局路径元素。
      返回:
      elements中的布局路径选择的布局。
      抛出:
      IllegalArgumentException - 如果布局路径对于此布局不符合格式
      IllegalArgumentException - 如果布局路径包含一个或多个解引用路径元素
      IllegalArgumentException - 如果布局路径包含一个或多个选择一个或多个序列元素索引的路径元素,例如MemoryLayout.PathElement.sequenceElement(long)预览MemoryLayout.PathElement.sequenceElement(long, long)预览
    • equals

      boolean equals(Object other)
      将指定的对象与此布局进行比较以确定是否相等。如果指定的对象也是一个布局,并且与此布局相等,则返回true。如果两个布局是相同类型、具有相同大小、名称和对齐约束,则它们被认为是相等的。此外,根据布局类型的不同,还必须满足其他条件:
      覆盖:
      equals 在类 Object
      参数:
      other - 用于与此布局比较相等的对象。
      返回:
      如果指定的对象等于此布局,则返回true
      参见:
    • hashCode

      int hashCode()
      返回此布局的哈希码值。
      覆盖:
      hashCode 在类 Object
      返回:
      此布局的哈希码值
      参见:
    • toString

      String toString()
      返回此布局的字符串表示形式。
      覆盖:
      toString 在类 Object
      返回:
      此布局的字符串表示形式
    • paddingLayout

      static PaddingLayoutPREVIEW paddingLayout(long byteSize)
      使用给定的字节大小创建填充布局。返回的布局的对齐约束为1。因此,在没有显式对齐约束的情况下,填充布局不会影响其嵌套到的组或序列布局的自然对齐。
      参数:
      byteSize - 填充大小(以字节表示)。
      返回:
      新的选择器布局。
      抛出:
      IllegalArgumentException - 如果byteSize <= 0
    • sequenceLayout

      static SequenceLayoutPREVIEW sequenceLayout(long elementCount, MemoryLayoutPREVIEW elementLayout)
      使用给定的元素布局和元素计数创建序列布局。
      参数:
      elementCount - 序列元素计数。
      elementLayout - 序列元素布局。
      返回:
      具有给定元素布局和大小的新序列布局。
      抛出:
      IllegalArgumentException - 如果elementCount为负数。
      IllegalArgumentException - 如果elementLayout.byteSize() * elementCount溢出。
      IllegalArgumentException - 如果elementLayout.byteSize() % elementLayout.byteAlignment() != 0
    • sequenceLayout

      static SequenceLayoutPREVIEW sequenceLayout(MemoryLayoutPREVIEW elementLayout)
      使用给定的元素布局和最大元素计数创建序列布局,以便不会溢出long。这等效于以下代码:
      sequenceLayout(Long.MAX_VALUE / elementLayout.byteSize(), elementLayout);
      
      参数:
      elementLayout - 序列元素布局。
      返回:
      具有给定元素布局和最大元素计数的新序列布局。
      抛出:
      IllegalArgumentException - 如果elementLayout.byteSize() % elementLayout.byteAlignment() != 0
    • structLayout

      static StructLayoutPREVIEW structLayout(MemoryLayoutPREVIEW... elements)
      使用给定的成员布局创建结构布局。
      API 注意:
      此工厂不会自动对齐元素布局,通过插入额外的填充布局预览元素。因此,以下结构布局创建将导致异常:
      structLayout(JAVA_SHORT, JAVA_INT);
      
      为避免异常,客户端可以插入额外的填充布局元素:
      structLayout(JAVA_SHORT, MemoryLayout.paddingLayout(2), JAVA_INT);
      
      或者,他们可以使用具有较小对齐约束的成员布局。这将导致紧凑结构布局:
      structLayout(JAVA_SHORT, JAVA_INT.withByteAlignment(2));
      
      参数:
      elements - 结构布局的成员布局。
      返回:
      具有给定成员布局的结构布局。
      抛出:
      IllegalArgumentException - 如果成员布局的字节大小之和溢出。
      IllegalArgumentException - 如果elements中的成员布局出现在(相对于结构布局起始处)与其对齐约束不兼容的偏移量处。
    • unionLayout

      static UnionLayoutPREVIEW unionLayout(MemoryLayoutPREVIEW... elements)
      使用给定的成员布局创建联合布局。
      参数:
      elements - 联合布局的成员布局。
      返回:
      具有给定成员布局的联合布局。