熟悉HotSpot中的对象
对象的内存布局
在HotSpot虚拟机中,对象的内存布局分为以下3个区域:
- 对象头
- 实例数据
- 对齐填充
对象头
对象头包含Mark Word和类型指针2个部分。如果是数组对象,则有一部分存储数组的长度。 Mark Word中存储:
- 哈希吗(HashCode)
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳
类型指针
类型指针Klass存储该对象的类对象的内存地址,可以通过该指针知道对象是属于哪个类。
实例数据
实例数据部分就是成员变量的值,其中包括父类成员变量和本类成员变量。
对齐填充
保证对象内存大小是8字节的倍数,不足的部分按填充对齐。
对象的创建过程
- 类加载检查 先检查对象的类是否加载,没有加载则先加载类。(类加载完成后就能计算出对象的大小,请参考Java如何计算Java对象大小
- 内存分配 给对象分配内存,如果使用TLAB,则优先在TLAB空间分配,再在Eden区分配。如果内存规整则使用指针碰撞方式分配内存,不规整则使用空闲列表方式分配内存。
- 指针碰撞 内存规整(说明垃圾回收采用的是标记整理算法),已使用内存和空闲内存分界线上放着一个指针左为分界指示器。只需要将指针移动往空闲内存一端移动对象大小的距离。
- 空闲列表 内存不规整(说明垃圾回收采用的是标记清除算法,有碎片),那么VM必须维护一个列表,记录哪些内存是空闲可用的,分配时从空闲列表中找到一块足够大的内存给对象示例使用。
- 初始化
- 设置零值 内存分配完成后,虚拟机会将分配到内存的空间都初始化为零值。(不包含对象头) 这也保证了Java代码不赋值初始化就直接使用,程序能访问到这些字段的数据类型对应的零值。
- 设置对象头信息 – 设置klass指针为对应类对象的地址 – 对象的hashCode – 对象的分代年龄 等
- 执行构造方法
<init>
执行对象所属类定义的init
构造方法,完成初始化。
至此,一个可用的对象才算是完全创建出来。
对象的访问方式
对象的存储空间是在堆中分配的,但是这个对象的引用(存的是地址)却是在栈中分配的。
Java程序通过栈上的reference
引用来操作堆上的具体对象。
目前对象的访问方式有句柄访问和直接指针访问方式。
句柄访问方式
对象引用内存储的是指向句柄地址,句柄对象中包含对象实例数据指针和类型数据指针。
直接指针访问方式
对象引用中存储的是对象实例数据指针,再通过对象头里的klass指针找到对象类型数据。
总结: 使用句柄方式访问,优点是对象变化时栈中的reference的值不用改变,存储的是稳定的句柄地址; 而使用直接指针方式访问最大好处就是速度更快,它节省了一次指针定位的时间开销。 HotSpot虚拟机采用直接指针的对象访问方式。
参考资料
- 周志明 * 《深入理解Java虚拟机》
- Java 虚拟机底层原理知识总结