0. 写在前面
基本概念
1. X 字节对齐: 占用内存长度是X的倍数,或者说是内存其实地址都是X的倍数。
2. 自身对齐值 : 按照数据自身长度来设置 1 中的X.比如在Java中byte是1,char是2 等
内存布局 5 大规则
- 规则1 : 任何对象内存都是8字节对齐的。如果必要则使用填充字段。
- 规则2 : 类属性在内存中的布局: 首先 longs and doubles; 接着 ints and floats; 接着 chars and shorts; 接着 bytes and booleans, 最后是 the references. 熟属性本身都是和自身长度对齐的。
- 规则3 : 不同阶层的类的属性永远不会进行交叉排序,首先是父类的属性(遵守规则2),然后是子类的属性。
- 规则4 : 在父类最后一个字段必须是4字节对齐(),如有必要使用填充字段。
- 规则5 : 当子类有double或long类型属性时,如果父类没有8字节对其,JVM将会打破规则2他会试图首先填充一个int ,然后是 shorts,然后是bytes ,reference,直到8字节对其然后按照规则2来进行布局
1. 没有任何属性的类内存布局
在Java虚拟机中任何对象(除了数组是要有长度)都有2 words(4字节)长的对象头。第一个Word中存储了自身的状态信息比如哈希吗,GC分代年龄,锁等信息,第二个Word中存储了类型指针,只想该实例对象的元数据(也就是表明是哪个类的实例)地址。同时任何对象的内存长度都是8字节对齐的。这也是对象内存布局的第一条规则。
规则1 : 任何对象内存都是8字节对齐的。如果必要则使用填充字段
由此我们可以知道如果一个类中没有任何属性那么在使用new NoProObject()
创建一个对象实例时只需要在Java堆上分配8字节的对象头就可以了,不需要额外的内存分配。
2. 直接继承子Object类的类实例对象内存布局
类的属性在8字节对象头的后面。在内存中属性都是占据自己长度大小一个的长度(不会进行8字节对齐),比如 : int 类型是4字节齐,long 8字节对齐。另外还有一个性能方面的原因:通常读取4字节内存数据到4字节的长度的寄存器中如果数据是4字节对齐的将会效率更高。
为了节省内存,内存中的属性并不是按照他们声明的顺序来排放的,他们会按照下面的顺序进行排放:
1. doubles and longs (8 bytes)
2. ints and floats (4 bytes)
3. shorts and chars (2 bytes 填充到4)
4. booleans and bytes (1 bytes 同上)
5. reference (4 bytes 同上)
上述方案优化了内存使用, 比如声明如下一个类
class MyClass {
byte a;
int c;
boolean d;
long e;
Object f;
}
如果JVM不进行属性重新排序,那么内存布局如下
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 4 bytes] 16
[d: 1 byte ] 17
[padding: 7 bytes] 24
[e: 8 bytes] 32
[f: 4 bytes] 36
[padding: 4 bytes] 40
可以看出有14个字节用来填充(浪费),对象总共使用了40个字节, 如果使用上面的的重排序方案,该对象在内存中的结构如下:
[HEADER: 8 bytes] 8
[e: 8 bytes] 16
[c: 4 bytes] 20
[a: 1 byte ] 21
[d: 1 byte ] 22
[padding: 2 bytes] 24
[f: 4 bytes] 28
[padding: 4 bytes] 32
优化后填充数据只有6字节,整个对象使用了32个字节。由此规则2出现
规则2 : 类属性在内存中的布局: 首先 longs and doubles; 接着 ints and floats; 接着 chars and shorts; 接着 bytes and booleans, 最后是 the references. 熟属性本身都是和自身长度对齐的
现在我们知道如何来计算一个直接继承子Object类的类实例对象占用的内存长度, 再来看一个java.lang.Boolean
类的内存布局
[HEADER: 8 bytes] 8
[value: 1 byte ] 9
[padding: 7 bytes] 16
一个Boolean实例对象占用16字节的内存,注意最后通过使用6字节的填充字段讲对象进行8字节对齐。
3. 继承其他类非Object类的类实例对象内存布局
接下来的三条规则是JVM用来组织有父类的类实例对象的属性在内存中的布局的。
规则3 : 不同阶层的类的属性永远不会进行交叉排序,首先是父类的属性(遵守规则2),然后是子类的属性。
来看个例子
class A {
long a;
int b;
int c;
}
class B extends A {
long d;
}
B实例对象的内存布局如下
[HEADER: 8 bytes] 8
[a: 8 bytes] 16
[b: 4 bytes] 20
[c: 4 bytes] 24
[d: 8 bytes] 32
规则4是用来处理父类字段没有4字节对齐的情况,规则4如下
规则4 : 在父类最后一个字段必须是4字节对齐(),如有必要使用填充字段
class A {
byte a;
}
class B {
byte b;
}
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12 (父类最后一个字段 a 后没有4字节对其,所以使用3个填充字段)
[b: 1 byte ] 13
[padding: 3 bytes] 16
最后一个规则是为了节省内存空间,当子类中有double或long时,如果父类没有8字节对齐,那么子类将打破规则2会先填充一个 int 然后是shorts 然后是 bytes 直到8字节对齐。
规则5 : 当子类有double或long类型属性时,如果父类没有8字节对其,JVM将会打破规则2他会试图首先填充一个int ,然后是 shorts,然后是bytes ,reference,直到8字节对其然后按照规则2来进行布局
class A {
byte a;
}
class B {
long b;
short c;
byte d;
}
内存布局
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12 (父类没有8字节对齐,子类将打破规则2)
[c: 2 bytes] 14
[d: 1 byte ] 15
[padding: 1 byte ] 16
[b: 8 bytes] 24
5. 数组的内存布局
数组类型的对象头有长度字段。数组元素和普通对象一个的内存布局
下面是长度为3的byte数组的内存布局
[HEADER: 12 bytes] 12
[[0]: 1 byte ] 13
[[1]: 1 byte ] 14
[[2]: 1 byte ] 15
[padding: 1 byte ] 16
长度为3的long数组
[HEADER: 12 bytes] 12
[padding: 4 bytes] 16
[[0]: 8 bytes] 24
[[1]: 8 bytes] 32
[[2]: 8 bytes] 40
6. 内部类
非静态内部类有一个隐含字段reference他是外部类的一个运用,和普通引用一样。基于此非静态内部类会多出4字节的内存占用。
class A{
class B {
}
}
B实例对象内存布局
[HEADER: 8 bytes] 8
[ref: 4 byte ] 12 (父类字段引用)
[padding: 4 bytes] 16
7. 总结
我们学习了如何计算32bit JVM中对象占用内存大小,了解了对象的内存布局将会帮助我们清楚我们的类将会占用多少内存。
原文链接 : https://awaiswaheed.wordpress.com/category/java-learning/java-core/java-object-memory-structure