java对象的内存结构
对象结构
如上图,java对象内存构成分成三部分,对象头、对象体、字节对齐。其中对象头包括:mark word(标记字)、class pointer(类对象指针)、array length(数组长度)
对象头
-
mark word
用来存储自身运行时的数据,例如:对象的分代年龄、hashCode、锁状态等信息
-
Class Pointer(类对象指针)
用来存储方法区中字节码对象的地址,JVM通过这个指针来确定这个对象是属于那个类的实例。
-
Array Length(数组长度)
如果对象是数组,则该字段记录数组的长度,如果不是数组,该字段不存在。是一个可选字段
对象体
对象的成员属性,也包括父类的成员属性,这部分按4字节对齐
字节对齐
对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存字节数为8的倍数HotSpot VM的内存管理要求对象起始地址必须是8字节的整数倍。对象头本身是8的倍数,当对象的实例变量数据不是8的倍数时,便需要填充数据来保证8字节的对齐
markWord的结构信息
java内置锁的很多信息都存放在对象的mark word字段中。
java内置锁
java有四种内置锁,级别由低到高以此为:无锁、偏向锁、轻量级锁、重量级锁。
在JDK1.6之前,java的锁一直用的重量级锁(重量级锁用的是操作系统底层的锁),但是很多共享资源的操作其实很快就运行完了,比如自增操作,而且一些共享资源往往只有一个线程访问,那么在这些情况下直接使用重量级锁,获取跟释放锁的效率就比较低,因为会设计用户态跟内核态的切换,两者的切换就会比较消耗资源。
所以对synchronized进行了优化,引入了偏向锁、轻量级锁。当然轻量级锁有自适应自旋锁的说法,这个不再讨论范围。
java内置的四种锁状态(无锁、偏向锁、轻量级锁、重量级锁)随着竞争的激烈而逐渐升级,而且是不可逆的,也就是说只能进行锁升级而不可降级。
markWord的结构信息
32位的Mark Word与64位的Mark Word结构相似,以64位的为例。
上图表示不同锁状态下的64位mark word的结构信息
-
lock
锁状态标记位,占两个二进制位,由于希望用尽可能少的二进制位表示尽可能多的信息,因此设置了lock标记。该标记的值不同,整个Mark Word表示的含义就不同
-
biased_lock
对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁
-
分代年龄(age)
4位的Java对象分代年龄。在GC中,对象在Survivor区复制一次,年龄就增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,因此最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因
-
对象hashCode(identity_hashcode)
31位的对象标识HashCode(哈希码)采用延迟加载技术,当调用Object.hashCode()方法或者System.identityHashCode()方法计算对象的HashCode后,其结果将被写到该对象头中。当对象被锁定时,该值会移动到Monitor(监视器)中
-
线程ID(threadId)
54位的线程ID值为持有偏向锁的线程ID
-
epoch
偏向时间戳
-
ptr_to_lock_record
占62位,在轻量级锁的状态下指向栈帧中锁记录的指针
-
ptr_to_heavyweight_monitor
占62位,在重量级锁的状态下指向对象监视器的指针