HotSpot中的对象
2019-02-09
1 对象创建过程
- 遇到new指令的时候,检查该指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个类是否已加载、解析、初始化过,如果没有必须先进行类的加载过程(这个之后再整理);
- 分配内存。加载类之后一个对象所需的内存大小就确定了;使用Serial、ParNew等收集器时,堆内存是整齐的,所以使用指针碰撞划分内存,即在空闲内存的分界点开始分配指定大小的内存空间;如果用CMS等给予Mark-Sweep算法的收集器时,堆内存不规整,所以使用空闲列表划分内存,即JVM维护了一个记录可用内存的表,从列表中找一块足够大小的内存空间用于分配,并更新列表记录。
- 对于多线程同时创建对象时,指针可能出错,可以使用两种解决方案:
- 对分配内存空间的动作进行同步,采用CAS+失败重试的方式可以保证操作的原子性。
- 把内存分配动作按照线程划分在不同的空间之中进行,即每个线程在java对中预先分配一小块内存成为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。当TLAB用完并分配新TLAB的时才需要同步锁定。
- 内存分配完成后,进行初始化零值(如果使用TLAB,可以在TLAB分配时进行);
- 虚拟机对对象进行设置,设置内容存放在对象头(Object Header);
- 执行
方法。
2 对象的内存布局
对象在内存中存储的布局有3块区域:对象头、实例数据和对齐填充。
2.1 对象头
- 存储对象自身运行时数据。如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为”Mark Word“。
- 类型指针。指向它的类元数据指针,通过这个指针可以确定该对象是那个类的实例。(并不是所有的虚拟机实现都有这个)
- 如果对象是一个Java数组,那么对象头中必须有一块用来记录数组长度。
2.2 实例数据
这部分是对象真正存储的有效信息,也是代码中定义的各种字段的内容。存储的顺序受虚拟机分配策略和字段在代码中定义的顺序共同影响。
2.3 对齐填充
占位符的作用,可有可无。
3 对象的访问定位
对象的访问定位有两种:句柄定位和直接指针。HotSpot用的是后者。
- 句柄定位:Java堆会画出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
- 直接指针访问:Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
对比:使用直接指针速度快;使用句柄时reference指向稳定的句柄,对象被移动时改变的也只是句柄中实例数据的指针,而reference本身并不需要修改。