9

我希望 Java 14 记录实际上比类似的数据类使用更少的内存。

他们还是使用相同的内存?

4

4 回答 4

7

添加到@lugiorgi执行的基本分析和我可以提出的分析字节码的类似显着差异是在toString,equalshashcode.

一方面,具有重写Object类 API 的现有类看起来像

public class City {
    private final Integer id;
    private final String name;
    // all-args, toString, getters, equals, and hashcode
}

产生如下字节码

 public java.lang.String toString();
    Code:
       0: aload_0
       1: getfield      #7                  // Field id:Ljava/lang/Integer;
       4: aload_0
       5: getfield      #13                 // Field name:Ljava/lang/String;
       8: invokedynamic #17,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/Integer;Ljava/lang/String;)Ljava/lang/String;
      13: areturn

  public boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: if_acmpne     7
       5: iconst_1
       6: ireturn
       7: aload_1
       8: ifnull        22
      11: aload_0
      12: invokevirtual #21                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      15: aload_1
      16: invokevirtual #21                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      19: if_acmpeq     24
      22: iconst_0
      23: ireturn
      24: aload_1
      25: checkcast     #8                  // class edu/forty/bits/records/equals/City
      28: astore_2
      29: aload_0
      30: getfield      #7                  // Field id:Ljava/lang/Integer;
      33: aload_2
      34: getfield      #7                  // Field id:Ljava/lang/Integer;
      37: invokevirtual #25                 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
      40: ifne          45
      43: iconst_0
      44: ireturn
      45: aload_0
      46: getfield      #13                 // Field name:Ljava/lang/String;
      49: aload_2
      50: getfield      #13                 // Field name:Ljava/lang/String;
      53: invokevirtual #31                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      56: ireturn

  public int hashCode();
    Code:
       0: aload_0
       1: getfield      #7                  // Field id:Ljava/lang/Integer;
       4: invokevirtual #34                 // Method java/lang/Integer.hashCode:()I
       7: istore_1
       8: bipush        31
      10: iload_1
      11: imul
      12: aload_0
      13: getfield      #13                 // Field name:Ljava/lang/String;
      16: invokevirtual #38                 // Method java/lang/String.hashCode:()I
      19: iadd
      20: istore_1
      21: iload_1
      22: ireturn

另一方面,相同的记录表示

record CityRecord(Integer id, String name) {}

产生的字节码少于

 public java.lang.String toString();
    Code:
       0: aload_0
       1: invokedynamic #19,  0             // InvokeDynamic #0:toString:(Ledu/forty/bits/records/equals/CityRecord;)Ljava/lang/String;
       6: areturn

  public final int hashCode();
    Code:
       0: aload_0
       1: invokedynamic #23,  0             // InvokeDynamic #0:hashCode:(Ledu/forty/bits/records/equals/CityRecord;)I
       6: ireturn

  public final boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: invokedynamic #27,  0             // InvokeDynamic #0:equals:(Ledu/forty/bits/records/equals/CityRecord;Ljava/lang/Object;)Z
       7: ireturn

注意:根据我在生成的访问器和构造器字节码上观察到的情况,它们对于表示形式都是相似的,因此也被排除在此处的数据之外。

于 2020-04-14T15:24:21.437 回答
2

我做了一些快速而肮脏的测试,如下

public record PersonRecord(String firstName, String lastName) {}

对比

import java.util.Objects;

public final class PersonClass {
    private final String firstName;
    private final String lastName;

    public PersonClass(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String firstName() {
        return firstName;
    }

    public String lastName() {
        return lastName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersonClass that = (PersonClass) o;
        return firstName.equals(that.firstName) &&
                lastName.equals(that.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }

    @Override
    public String toString() {
        return "PersonRecord[" +
                "firstName=" + firstName +
                ", lastName=" + lastName +
                "]";
    }
}

编译后的记录文件为1.475字节,类为1.643字节。大小差异可能来自不同的 equals/toString/hashCode 实现。

也许有人可以做一些字节码挖掘......

于 2020-04-14T14:08:26.370 回答
2

正确,我同意 [@lugiorgi] 和 [@Naman],记录和等效类之间生成的字节码的唯一区别在于方法的实现toStringequalshashCode. 在记录类的情况下,使用调用动态(indy)指令来实现类的相同引导方法:(java.lang.runtime.ObjectMethods新添加到记录项目中)。与调用 3 个不同的引导方法相比,这三个方法、toStringequals调用hashCode相同的引导方法在类文件中节省了更多空间。当然,正如其他答案中已经显示的那样,比生成明显的字节码节省更多空间

于 2020-04-30T04:59:39.480 回答
2

Java 中的每个对象都有 64 位元数据,因此对象数组将比记录数组消耗更多内存,因为元数据将仅附加到数组引用中,而不是每个记录/结构中。此外,优势应该在于可以从垃圾收集器管理记录内存的方式,因为它是固定且连续的。这就是我的理解,如果有人可以确认或添加额外的信息将非常有用。谢谢

于 2021-01-14T19:00:57.357 回答