问题
I’m hoping that Java 14 records actually use less memory than a similar data class.
Do they or is the memory usage the same?
回答1:
To add to the basic analysis performed by @lugiorgi and a similar noticeable difference that I could come up with analyzing the byte code, is in the implementation of toString
, equals
and hashcode
.
On one hand, previously used class with overridden Object
class APIs looking like
public class City {
private final Integer id;
private final String name;
// all-args, toString, getters, equals, and hashcode
}
produces the byte code as following
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
On the other hand the record representation for the same
record CityRecord(Integer id, String name) {}
produces the bytecode as less as
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
Note: To what I could observe on the accessors and constructor byte code generated, they are alike for both the representation and hence excluded from the data here as well.
回答2:
I did some quick and dirty testing with following
public record PersonRecord(String firstName, String lastName) {}
vs.
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 +
"]";
}
}
The compiled record file amounts to 1.475 bytes, the class to 1.643 bytes. The size difference probably comes from different equals/toString/hashCode implementations.
Maybe someone can do some bytecode digging...
回答3:
correct, I agree with [@lugiorgi] and [@Naman], the only difference in the generated bytecode between a record and the equivalent class is in the implementation of methods: toString
, equals
and hashCode
. Which in the case of a record class are implemented using an invoke dynamic (indy) instruction to the same bootstrap method at class: java.lang.runtime.ObjectMethods
(freshly added in the records project). The fact that these three methods, toString
, equals
and hashCode
, invoke the same bootstrap method saves more space in the class file than invoking 3 different bootstraps methods. And of course as already shown in the other answers, saves more space than generating the obvious bytecode
来源:https://stackoverflow.com/questions/61208493/do-java-14-records-actually-save-memory-over-a-similar-class-declaration-or-are