本文将通过Instrumentation + sa-jdi来分析几个实例的内存对象布局

关键概念

  1. Java对象的内存布局:对象头(Header) + 实例数据(Instance Data) + 对其填充(Padding
  2. Header具体内容
    • Mark Word:存储对象hashCode、锁信息等
    • Class MetaData Address:存储对象类型数据的指针
    • Array Length:数组的长度(如果当前对象是数组,否则没有这一内容)
  3. HotSpot JVM中对象的对齐方式:8字节对齐
  4. 实例域重排序:为了节省内存空间,实例域将按(double/longint/floatshort/charboolean/bytereference)进行重排序
  5. 非静态内部类:有一个隐藏的对外部类的引用
  6. 指针压缩:影响对象的内存布局,JVM参数:-XX:+UseCompressedOops

指针压缩的影响

是否开启压缩 Mark Word Class MetaData Address Reference
8 8 8
8 4 4

指针压缩

代码

1
2
3
4
5
6
public class CompressedOopsTestClass {

int intValue;
Integer integerRef;
Integer[] integerArrayRef = new Integer[3];
}

开启压缩

运行结果

1
me.zhongmingmao.create.classes.CompressedOopsTestClass : shallow size = 24 Bytes , retained size= 56 Bytes

内存映像

1
2
3
4
5
6
7
8
9
10
11
12
13
Oop for me/zhongmingmao/create/classes/CompressedOopsTestClass @ 0x00000007bffe4e78 (object size = 24)
- _mark: {0} :392050833673
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/CompressedOopsTestClass
- intValue: {12} :0
- integerRef: {16} :null
- integerArrayRef: {20} :ObjArray @ 0x00000007bffe4e90

ObjArray @ 0x00000007bffe4e90 (object size = 32)
- _mark: {0} :490473115401
- _metadata._compressed_klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/Integer
- 0: {16} :null
- 1: {20} :null
- 2: {24} :null

对象布局

关闭压缩

运行结果

1
me.zhongmingmao.create.classes.CompressedOopsTestClass : shallow size = 40 Bytes , retained size= 88 Bytes

内存映像

1
2
3
4
5
6
7
8
9
10
11
12
13
Oop for me/zhongmingmao/create/classes/CompressedOopsTestClass @ 0x000000011602d7f8 (object size = 40)
- _mark: {0} :392050833665
- _metadata._klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/CompressedOopsTestClass
- intValue: {16} :0
- integerRef: {24} :null
- integerArrayRef: {32} :ObjArray @ 0x000000011602da20

ObjArray @ 0x000000011602da20 (object size = 48)
- _mark: {0} :490473115393
- _metadata._klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/Integer
- 0: {24} :null
- 1: {32} :null
- 2: {40} :null

对象布局

实例域重排序

代码

1
2
3
4
5
6
7
8
9
10
11
12
public class ReorderingTestClass {
Object objectRef;
Integer integerRef;
int intValue_1;
int intValue_2;
byte byteValue_1;
byte byteValue_2;
byte byteValue_3;
short shortValue_1;
long longValue_1;
Long[] longArrayRef;
}

运行结果

1
2
# 默认进行指针压缩
me.zhongmingmao.create.classes.ReorderingTestClass : shallow size = 48 Bytes , retained size= 48 Bytes

内存映像

1
2
3
4
5
6
7
8
9
10
11
12
13
Oop for me/zhongmingmao/create/classes/ReorderingTestClass @ 0x00000007bfe2c750 (object size = 48)
- _mark: {0} :40809812993
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/ReorderingTestClass
- objectRef: {36} :null
- integerRef: {40} :null
- intValue_1: {12} :0
- intValue_2: {24} :0
- byteValue_1: {30} :0
- byteValue_2: {31} :0
- byteValue_3: {32} :0
- shortValue_1: {28} :0
- longValue_1: {16} :0
- longArrayRef: {44} :null

对象布局

1. 实例域重排序的主要目的是为了`让内存更为紧凑`,因为`Hotspot JVM`在内存中是`8Byte对齐`,必然会出现上面左图那样的内存浪费 2. 大致原则上是按照`double/long`,`int/float`,`short/char`,`boolean/byte`,`reference`的优先级进行重排序,但还有进一步优化,如果有比自身优先级低的但能填充自己无法填充的区域,则让优先级低的进行填充(表述有点拗口) - 如右图中,`12`的位置原本尝试填充`longValue_1`,但由于`long需要8 Bytes`和`字节对齐`,无法填充,而`intValue_1`恰好能填充这原本会被浪费的空间,让内存更为紧凑 3. 从上图可知,进行实例域重排序能更有效的利用内存

内部类

代码

1
2
3
4
5
6
7
8
9
10
11
public class OuterClass {
InnerClass innerClassRef;

public OuterClass() {
this.innerClassRef = new InnerClass();
}

class InnerClass {
Integer integerRef;
}
}

运行结果

1
me.zhongmingmao.create.classes.OuterClass : shallow size = 16 Bytes , retained size= 40 Bytes

内存映像

1
2
3
4
5
6
7
8
9
10
Oop for me/zhongmingmao/create/classes/OuterClass @ 0x00000007bfe30b40 (object size = 16)
- _mark: {0} :128250200577
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/OuterClass
- innerClassRef: {12} :Oop for me/zhongmingmao/create/classes/OuterClass$InnerClass @ 0x00000007bfe30b50

Oop for me/zhongmingmao/create/classes/OuterClass$InnerClass @ 0x00000007bfe30b50 (object size = 24)
- _mark: {0} :47710727425
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/OuterClass$InnerClass
- integerRef: {12} :null
- this$0: {16} :Oop for me/zhongmingmao/create/classes/OuterClass @ 0x00000007bfe30b40

对象布局

`非静态内部类`隐藏有一个的对`外部类的引用`(`this$0`)

继承

代码

1
2
3
4
5
6
7
8
9
10
public class Father {

int intValue;
Integer integerRef;

public Father() {
integerRef = new Integer(1 << 10);
}

}
1
2
3
4
5
6
public class Son extends Father {

byte byteValue;
short shortValue;
Integer[] integerArrayRef = new Integer[3];
}
1
2
3
4
5
6
7
8
9
10
11
12
public class GranSon extends Son {

boolean booleanValue;
Father[] fatherArrayRef = new Father[3];

public GranSon() {
for (int i = 0; i < fatherArrayRef.length; ++i) {
fatherArrayRef[i] = new Father();
}
}

}

运行结果

1
2
3
me.zhongmingmao.create.classes.Father : shallow size = 24 Bytes , retained size= 40 Bytes
me.zhongmingmao.create.classes.Son : shallow size = 32 Bytes , retained size= 80 Bytes
me.zhongmingmao.create.classes.GranSon : shallow size = 40 Bytes , retained size= 240 Bytes

内存映像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 只摘录了GranSon对象的完整记录,对于Son和Father的分析也是类似的
Oop for me/zhongmingmao/create/classes/GranSon @ 0x00000007bfe364e8 (object size = 40)
- _mark: {0} :116709573121
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/GranSon
- intValue: {12} :0
- integerRef: {16} :Oop for java/lang/Integer @ 0x00000007bfe36510
- byteValue: {22} :0
- shortValue: {20} :0
- integerArrayRef: {24} :ObjArray @ 0x00000007bfe36520
- booleanValue: {28} :false
- fatherArrayRef: {32} :ObjArray @ 0x00000007bfe365a8

# integerRef
Oop for java/lang/Integer @ 0x00000007bfe36510 (object size = 16)
- _mark: {0} :1
- _metadata._compressed_klass: {8} :InstanceKlass for java/lang/Integer
- value: {12} :1024

# integerArrayRef
ObjArray @ 0x00000007bfe36520 (object size = 32)
- _mark: {0} :440814568449
- _metadata._compressed_klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/Integer
- 0: {16} :null
- 1: {20} :null
- 2: {24} :null

# fatherArrayRef
ObjArray @ 0x00000007bfe365a8 (object size = 32)
- _mark: {0} :131009079297
- _metadata._compressed_klass: {8} :ObjArrayKlass for InstanceKlass for me/zhongmingmao/create/classes/Father
- 0: {16} :Oop for me/zhongmingmao/create/classes/Father @ 0x00000007bfe365c8
- 1: {20} :Oop for me/zhongmingmao/create/classes/Father @ 0x00000007bfe365f0
- 2: {24} :Oop for me/zhongmingmao/create/classes/Father @ 0x00000007bfe36618

# fatherArrayRef[0]
Oop for me/zhongmingmao/create/classes/Father @ 0x00000007bfe365c8 (object size = 24)
- _mark: {0} :306715851521
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/Father
- intValue: {12} :0
- integerRef: {16} :Oop for java/lang/Integer @ 0x00000007bfe365e0

Oop for java/lang/Integer @ 0x00000007bfe365e0 (object size = 16)
- _mark: {0} :1
- _metadata._compressed_klass: {8} :InstanceKlass for java/lang/Integer
- value: {12} :1024

# fatherArrayRef[1]
Oop for me/zhongmingmao/create/classes/Father @ 0x00000007bfe365f0 (object size = 24)
- _mark: {0} :54816361729
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/Father
- intValue: {12} :0
- integerRef: {16} :Oop for java/lang/Integer @ 0x00000007bfe36608

Oop for java/lang/Integer @ 0x00000007bfe36608 (object size = 16)
- _mark: {0} :1
- _metadata._compressed_klass: {8} :InstanceKlass for java/lang/Integer
- value: {12} :1024

# fatherArrayRef[2]
Oop for me/zhongmingmao/create/classes/Father @ 0x00000007bfe36618 (object size = 24)
- _mark: {0} :101599592961
- _metadata._compressed_klass: {8} :InstanceKlass for me/zhongmingmao/create/classes/Father
- intValue: {12} :0
- integerRef: {16} :Oop for java/lang/Integer @ 0x00000007bfe36630

Oop for java/lang/Integer @ 0x00000007bfe36630 (object size = 16)
- _mark: {0} :1
- _metadata._compressed_klass: {8} :InstanceKlass for java/lang/Integer
- value: {12} :1024

对象布局

1. `父类实例字段在子类实例字段前面`,只有`类自身定义的实例域`才会进行`重排序`,不会跨域到父类或子类 - 例如在GranSon的`23`的位置其实可以存放`booleanValue`,但由于来自Son的实例域还有`integerArrayRef`没有填充完,因此只能是`padding` - 因此,`子类实例域不会插入到父类实例域的空隙中` 2. retained size= 240 Bytes - 240 = (24 + 16) * 3 + 32 + 32 +16 + 40