1.对象内存划分:
在Hotspot虚拟机中,对象的内存布局可以划分为三个部分:
1. 对象头
2. 实例数据
3. 对齐填充
2.对象头
对象头中储存了两类信息:
- 对象自身运行时的数据,官方称为“Mark Word”,包括哈希码、GC分代年龄、锁状态标志、线程持有的锁等等。在32位和64位虚拟机中,Mark Word占据的内存空间分别为32个bit和64个bit,即4个字节和8个字节。
- 类型指针,官方称为“Klass Pointer”,即对象指向它的类型元数据的指针。在虚拟机默认开启指针压缩的情况下,Klass Pointer占据4byte,如果使用-XX:-UseCompressedOops关闭指针压缩,Klass Pointer占据8byte
对于32位虚拟机,25个比特用来储存对象哈希码,4个比特用于储存对象分代年龄,2个比特用于储存锁标志位,1个比特固定为0。查看JDK源码markOop.cpp文件:
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
对于64位虚拟机,25个比特为空,31个比特用来储存对象哈希码,1个比特为空,4个比特用于储存对象分代年龄,1个比特用于储存偏向锁信息,2个比特储存锁标志位。
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
储存对象分代年龄的空间为4个比特,也可以解释为什么虚拟机垃圾回收机制中默认熬过15次GC的新生代对象会进入老年代。
对象按锁状态可以分为五类:
- 未锁定
- 偏向锁锁定
- 轻量级锁定
- 重量级锁定
- GC标记
64位虚拟机中用1个比特用于储存偏向锁信息,2个比特储存锁标志位,共计3个比特用于表示对象的5种锁状态。
3.实例数据
实例数据部分是对象真正储存的有效信息,即我们在程序代码中所定义的字段内容。这一部分的内存大小按照字段多少来确定,是不固定的,如果对象中不包含任何字段内容,则为空。
4.对齐填充
这一部分和实例数据部分一样,也不是必须存在的,它没有特殊的含义,只是起到占位符的作用。在Hotspot虚拟机中规定,任何对象的大小都必须是8个字节的整数倍,如果对象头加上实例数据的大小不足8的整数倍,则通过对齐填充来进行补全。
5.通过JOL查看对象内存结构
jol是一种可以查看对象结构的jar包,其maven依赖如下:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
测试代码如下:
public class Head {
boolean flag = true;
public static void main(String[] args) {
Head head = new Head();
System.out.println(ClassLayout.parseInstance(head).toPrintable());
}
}
这里需要注意的是,如果不对head对象进行hashcode计算,得到的对象信息里储存hashcode的比特为都为0:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
12 1 boolean Head.flag true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
所以先进行hashcode计算,并转化为16进制:
public class Head {
boolean flag = true;
public static void main(String[] args) {
Head head = new Head();
System.out.println(Integer.toHexString(head.hashCode()));
System.out.println(ClassLayout.parseInstance(head).toPrintable());
}
}
结果:
330bedb4
com.kuangshen.Object_head.Head object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 b4 ed 0b (00000001 10110100 11101101 00001011) (200127489)
4 4 (object header) 33 00 00 00 (00110011 00000000 00000000 00000000) (51)
8 4 (object header) 05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
12 1 boolean Head.flag true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
这里又涉及一个小端储存和大端储存的问题:
- 小端储存:数据的高位字节存放在地址的高端 低位字节存放在地址低端
- 大端储存:数据的高位字节存放在地址的低端 低位字节存放在地址高端
因为采用的小端储存,所以对象头信息储存顺序和markOop.cpp中所说明的顺序相反,所以按大端储存,对象头信息为:
00000000 00000000 00000000 00110011 00001011 11101101 10110100 00000001
各部分信息都能对应上。
参考资料:
1.《深入理解Java虚拟机》第三版 周志明