测试环境
Darwin MacBook 19.4.0 Darwin Kernel Version 19.4.0
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
对象头(object header)
object header
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format. https://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
由上面的引用可知JAVA对象头包含 Mark Word 和 Klass Pointer 两部分,其中数组对象除了这两部分之外还包含数组的长度(int 型,占 4 字节)。
Mark Word
mark word
The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.
http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/oops/markOop.hpp
根据官方的资料可知 mark word 在不同平台占用的空间大小是一个 word size (32/64bit),存储了对象哈希吗、GC分代年龄
Klass Pointer
klass pointer
The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".
klass pointer 是对象指向它的类元数据的指针,虚拟机通过它来确定这个对象是哪个类的实例。 在 32-bit JVM 上的大小是 4 bytes,在 64-bit JVM上可以是 4 bytes,也可以是 8 bytes,由 JVM 参数“是否压缩原始对象”决定,在HotSpot中是UseCompressedOops参数(jdk1.8 和jdk1.9默认是开启的)。
实例数据
JAVA 对象中的各种实例数据所占字节大小
类型 | 大小 |
---|---|
Object Reference | word size |
byte | 1 byte |
boolean | 1 byte |
char | 2 bytes |
short | 2 bytes |
int | 4 bytes |
float | 4 bytes |
double | 8 bytes |
long | 8 bytes |
无从是从父类继承下来的字段还是定义在自身中的字段都会被记录。为了避免空间浪费,默认情况下,分配的优先依次顺序是:double / long > int > float > char/ short > byte / boolean > oops。可以看出相同宽度的字段总是会被分配到一起,尽可能先分配占用空间大的类型。但是如果 CompactFields 参数为 true (默认为 true ),那么宽度较窄的字段也可能会插到前面,详情见示例三和四。
如果对象中没有声明成员字段且也没有从父类继承字段,对象在堆内存中是不存在实例数据的。
对齐填充
JAVA 虚拟机要求对象起始地址必须是 8 字节的整数倍,也即是对象在在堆内存中所占字节大小(也就是上面的两项)必须是 8 字节的整数倍,如果不是 8 字节的倍数,差多少会字节则会被补齐填充。 如果上面的两项大小之和刚好是 8 的倍数则无需对齐填充。
使用 JOL 查看对象内存布局
JOL 介绍
JOL (Java Object Layout) is the tiny toolbox to analyze object layout schemes in JVMs. These tools are using Unsafe, JVMTI, and Serviceability Agent (SA) heavily to decoder the actual object layout, footprint, and references. This makes JOL much more accurate than other tools relying on heap dumps, specification assumptions, etc.
JOL Maven 坐标
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
示例一
public class ObjLayoutDemo {
public static void main(String[] args) throws Exception {
Obj obj = new Obj();
String printable = ClassLayout.parseClass(obj.getClass()).toPrintable();
System.out.println(printable);
}
static class Obj{}
}
//out 空对象 对象头占 12 字节(jdk1.8 默认开启了压缩指针参数),还有 4 字节的对齐填充
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
示例二
public class ObjLayoutDemo {
public static void main(String[] args) throws Exception {
Obj[] objArr = new Obj[]{new Obj()};
String printable = ClassLayout.parseClass(objArr.getClass()).toPrintable();
System.out.println(printable);
}
static class Obj{}
}
//out 数组对象的对象头多了 4 字节用来保存数组长度
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 0 com.hujiwei.obj.layout.ObjLayoutDemo$Obj ObjLayoutDemo$Obj;.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
示例三
public class ObjLayoutDemo {
public static void main(String[] args) throws Exception {
Obj obj = new Obj();
String printable = ClassLayout.parseClass(obj.getClass()).toPrintable();
System.out.println(printable);
}
static class Obj {
byte a;
short b;
boolean c;
int d;
double e;
float f;
long l;
char j;
}
}
//out 从输出结果看字段的分配顺序并不是上面的默认策略,而是 int 插入到 double前面了,这是因为默认开启了 -XX:+CompactFields 参数。 object header 12 字节后面会有 4 字节的填充,刚好 int 可以放的下,如果没有 int 则会由其他 <= 4 字节的字段来填充。
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int Obj.d N/A
16 8 double Obj.e N/A
24 8 long Obj.l N/A
32 4 float Obj.f N/A
36 2 short Obj.b N/A
38 2 char Obj.j N/A
40 1 byte Obj.a N/A
41 1 boolean Obj.c N/A
42 6 (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
示例四
public class ObjLayoutDemo {
public static void main(String[] args) throws Exception {
Obj obj = new Obj();
String printable = ClassLayout.parseClass(obj.getClass()).toPrintable();
System.out.println(printable);
}
static class Obj {
byte a;
short b;
boolean c;
int d;
double e;
float f;
long l;
char j;
}
}
//out 关闭 -XX:-CompactFields 参数,就会按照默认的顺序来分配了,这样会在 object header 后面多出 4 字节的填充。
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (alignment/padding gap)
16 8 double Obj.e N/A
24 8 long Obj.l N/A
32 4 int Obj.d N/A
36 4 float Obj.f N/A
40 2 short Obj.b N/A
42 2 char Obj.j N/A
44 1 byte Obj.a N/A
45 1 boolean Obj.c N/A
46 2 (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 4 bytes internal + 2 bytes external = 6 bytes total