对空间的分配,虚拟机采用的两种方式保证原子性
CAS加失败重试的方式
CAS compare and swap 先比较再交换,比较原值是否一直来保证原子性。
失败重试就会再次调用新的值再来重试
本地线程分配缓存(TLAP Thread Local Allocation Buffer)
每个线程都会在内存空间预先分配一小块内存。虚拟机将分配的内存地址都置零,这样类对象的实例字段在java中就可以不负初值就可以直接使用,程序可以直接访问到所属数据类型的零值。
对象的内存布局
对象头(header)
对象头部分包括两类信息。
第一类是用于存储对象自身的运行时数据,如哈 希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,称为“Mark Word”;
对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针。
如果对象是个数组还有一块用于记录数组长度的数据(数组的长度是一种属性,而非方法)
实例数据(Instance Data)
对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字 段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
对齐填充(Padding)
这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作 用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是 任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者 2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
对象的访问定位
句柄
Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。
优点:reference中存储的是稳定句柄地 址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference本身不需要被修改
直接指针
Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销。
优点:是速度更快,它节省了一次指针定位的时间开销。