2 HotSpot虚拟机中的对象
2.1 对象的创建
1. 虚拟机遇到一条new指令时, 会先检查该指令的参数是否能定位到常量池中的类的符号引用, 检查这个符号引用的类是否已经加载、解析、初始化, 如果没有,需要进行类的加载过程.
2. 类加载完成后, 需要为对象分配内存空间, 对象所需要的内存空间在类加载完成后就完全确定.
分配内存的方式:
1. 指针碰撞:如果堆内存绝对规整,已使用的内存放在一边,未使用的放在另一边,分配新内存就是移动分界点的指示器.
2. 空闲列表:如果堆内存不规整,需要维护一个列表记录未使用的内存区域,查找分配一个足够大区域给新对象,然后更新列表.
堆是否规整取决于垃圾回收器是否具有压缩整理功能.
分配内存的过程如何避免并发安全问题:
1.对分配内存的过程进行加锁 (虚拟机通过CAS加上失败重试保证更新操作的原子性).
2.TLAB(可通过参数开启):提前按照线程分配不同的堆空间, 称为本地线程分配缓冲.哪个线程需要分配对象内存就在哪个线程中分配,只有当TLAB用尽, 产生新的TLAB时才需要加锁. 对象内存空间分配完成后, 会将该空间置为零值, 若果需要TLAB, 该过程提前到TLAB分配时.此时对象的属性已经被赋予零值.
3. 对象的初始化, 对象已经产生, 但由于没有调用构造方法, 此时的对象还不是我们想要的.等到<init>
方法调用完成该对象才算结束创建, 并可真正的被使用.
2.2 对象的内存布局
堆中的对象包含3部分:
1. 对象头:
- Mark Word
- 类信息的指针:不一定有, 这取决于对象的访问方式, 使用句柄池进行访问的话, 对象的对象头是不会去存储指向方法区中类型数据的指针的, 详情在下文的2.3可见.
- 数组长度:不一定有, 当堆中存储的是数组对象时,会有该部分.
2. 实例数据:对象真正存储的有效数据, 各种类型字段(包括父类继承的)
3. 对齐填充:该部分并不是必须的, 起到占位符的作用, hotspot要求对象占用的空间需要是8个字节的整数倍,对象头正好符合, 如果实例数据没有对齐, 就通过对齐填充补全.
下图是mark word在64位虚拟机中, 在不同锁状态的记录.

关于不同的锁状态, 涉及Java中锁的膨胀, 以后会提这件事.
2.3 对象的访问定位
栈中的引用型变量需要定位到堆中的对象,
使用句柄
在堆中创建句柄池, 在句柄池中拥有对象实例数据指针和对象类型数据指针, 对象实例数据指针指向堆中的对象, 对象类型指针指向方法区中对象的类型信息.
直接访问
直接访问时, 堆中不需要创建句柄池, 栈中引用型变量直接指向堆中的对象, 在对象中的对象头里存储指向对象所属类型的指针, 用来访问方法区的类型数据.


两种访问方式各有优势, 使用句柄池虽然会多一次指针定位的开销, 但是当堆中对象位置发生改变时, 只需要改变句柄池中对象的引用, 不需要改动栈中局部变量表的reference变量的指向; 直接访问的方式与之优势相反.HotSpot使用的是直接访问的方式.