Java面试题

题目

谈谈对象头的内存布局的理解,并谈谈你对 java 锁升级的了解

答案

Java 对象分为对象头,实例数据和对齐补充。对象头跟网络的协议报文头很像,划分很多区间,并且根据对象的状态复用自己的存储空间。

在 64 位虚拟机中对象头里面用 2 位表示的锁的标识位,1 位表示了锁的偏量标识,4 位表示了垃圾回收次数(所以垃圾回收的次数一般都是 15 次)。

名称 是否偏量锁位 锁标志位 场景
无锁 0 01 没有线程访问
偏向锁 1 01 一个线程访问
轻量级锁 0 00 两个线程访问
重量级锁 0 10 超过两个线程访问

偏向锁和轻量级锁都是乐观锁,重量级锁是悲观锁。

一个对象刚刚开始实例化的时候,没有任何线程来访问它的时候,它是可偏向的,意味着它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。

偏向第一个线程,这个线程再修改对象头成为偏向锁的时候使用 CAS 操作,并将对象头中的 ThreadId 改成自己的 ID,之后再次访问该对象的时候,只需要对比线程 ID ,不需要再进行 CAS 操作。

一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程会看这个对象的偏向状态,如果是偏向状态就检查原来持有该锁的线程的状态是否存活。如果持有当前对象的偏向锁的线程挂了,就将当前线程获取偏向锁。

如果原来的线程还活着,就马上执行线程操作栈,来判断线程是否还需要占用当前对象,如果仍然需要使用,就将偏向锁升级为轻量级锁。如果不存在了就将对象恢复成无锁状态,然后将当前线程获取锁,对象头里面记录的线程 ID 就是当前线程的 ID。

轻量级锁认为竞争状态存在,但是竞争程度很轻,一般两个线程对于同一个对象的操作都会错开,如果不巧发生在一起,就会自旋,另一个线程就会释放锁。如果这个时候又有第三个线程或者多个线程来访问时,就会变为重量级锁。重量级锁除了获取锁的线程执行外其它的锁都会放到队列里面,阻塞,防止 CPU 空转。