JVM中的java对象头

注意:在没有特殊说明的情况下,都是32 bits为例。

上一小节主要介绍了java中synchronized关键字的使用方法,而在这一小节中将介绍一下synchronized 在JVM中的实现基础——java对象头中的Mark Word

表1   Java对象头的长度

内容

说明

备注

Mark Word

存储对象的Mark Word信息

-

Class Metadata Address

存储指向对象存储类型的指针

-

Array Length

数组的长度

只有数组对象有该属性

synchronized使用的锁是存放在Java对象头中的Mark Word中,OpenJDK中的markOop.hpp 头文件详细介绍了Mark Word的内容,下面将分析32 bits的JVM中的Mark Word的构成。

表2   32位JVM的Mark Word存储结构

锁状态

23 bits

2 bits

4 bits

1 bit

2 bits

轻量级锁

指向栈中锁记录的指针

00

无锁状态

hash code

分代年龄

0

01

偏向锁

Thread ID

epoch

分代年龄

1

01

重量级锁

指向监视器(monitor)的指针

10

GC标记

0

11

:最后两位为锁标记位,倒数第三位是偏向标记,如果是1表示是偏向锁;合并单元格的位数就是 该字段的位数,例如hash code共25(23+2)位。

另外,对于偏向锁,如果Thread ID = 0,表示未加锁

JVM锁的类型及对比

Java 1.6对synchronized进行了大幅度的优化,其性能也有了大幅度的提升。Java 1.6引入 “偏向锁”和“轻量级锁”的概念,减少了获得锁和释放锁的消耗。在Java 1.6之后,加上原有的重量 级锁,锁一共有4种状态,分别是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。锁只能 按照上述的顺序进行升级操作,锁只要升级之后,就不能降级。

下面将分别介绍一下偏向锁、轻量级锁和重量级锁,并探索一下偏向锁升级为轻量级锁(revoke bias) 的流程和轻量级锁升级为重量级锁(inflate)的流程。偏向锁、轻量级锁的状态转化及对象 Mark Word的关系如下图所示。

图片来源:Synchronization and Object Locking文章中的配图

图1   偏向锁、轻量级锁的状态转化及对象Mark Word的关系

1. 偏向锁

偏向锁是Java 1.6新添加的内容,并且是jdk默认启动的选项,可以通过-XX:-UseBiasedLocking 来关闭偏向锁。另外,偏向锁默认不是立即就启动的,在程序启动后,通常有几秒的延迟,可以通过命令 -XX:BiasedLockingStartupDelay=0来关闭延迟。

如果JVM支持偏向锁,那么将按照下图所示的流程分配对象,加偏向锁。

图片来源:Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing第3页的配图

图2   偏向锁中的Mark Word的状态转化图

注意:这是简化版的流程图,因为偏向锁的图中缺少了epoch字段。

1.1 偏向锁的加锁

如果JVM支持偏向锁,那么在分配对象时,分配一个可偏向而未偏向的对象(Mark Word的最后3位 为101,并且Thread ID字段的值为0)。

然后,当一个线程访问同步块并获取锁时,将通过CAS(Compare And Swap)来尝试将对象头中的 Thread ID字段设置为自己的线程号,如果设置成功,则获得锁,那么以后线程再次进入和退出 同步块时,就不需要使用CAS来获取锁,只是简单的测试一个对象头中的Mark Word字段中是否存储 着指向当前线程的偏向锁;如果使用CAS设置失败时,说明存在锁的竞争,那么将执行偏向锁的撤销操作 (revoke bias),将偏向锁升级为轻量级锁。

:代码请查看biasedLocking.cpp中的revoke_and_rebias方法

1.2 偏向锁的升级

下面结合代码(有缩减)分析一下偏向锁升级为轻量级锁的过程,这里暂时不考虑批量撤销偏向 (bulk revocation)的情况。详细代码请查看biasedLocking.cpp中的revoke_bias方法。 偏向锁的撤销操作需要在全局检查点(global safepoint)执行,在全局检查点上没有 线程执行字节码。

:偏向锁的撤销的入口函数是biasedLocking.cpp中的revoke方法, 之后会通过VMThread调用revoke_bias方法

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias,
                            bool is_bulk, JavaThread* requesting_thread) {
  markOop mark = obj->mark();
  // 检查是否可偏向
  if (!mark->has_bias_pattern()) {
    return BiasedLocking::NOT_BIASED;
  }
  uint age = mark->age();
  markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
  markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
  JavaThread* biased_thread = mark->biased_locker();
  if (biased_thread == NULL) {
    // 可偏向但是未偏向的情况
    // 可能的使用场景为:因计算hash code而撤销偏向
    if (!allow_rebias) {
      obj->set_mark(unbiased_prototype);
    }
    return BiasedLocking::BIAS_REVOKED;
  }
  // 判断对象现在偏向的线程是否还存在
  // 即对象头中Mark Word中Thread ID字段指向的线程是否存在
  bool thread_is_alive = false;
  if (requesting_thread == biased_thread) {
    // 请求的线程拥有偏向锁
    thread_is_alive = true;
  } else {
   // 请求的线程不拥有偏向锁,递归查询
    for (JavaThread* cur_thread = Threads::first();
        cur_thread != NULL; cur_thread = cur_thread->next()) {
      if (cur_thread == biased_thread) {
        thread_is_alive = true;
        break;
      }
    }
  }
  if (!thread_is_alive) {
    if (allow_rebias) {
      obj->set_mark(biased_prototype);
    } else {
      obj->set_mark(unbiased_prototype);
    }
    return BiasedLocking::BIAS_REVOKED;
  }
  // 拥有偏向锁的线程仍然存活
  // 检查该线程是否拥有锁:
  //    如果拥有锁,那么需要升级为轻量级锁,然后将displaced mark word复制到线程栈中;
  //    如果不再拥有锁,如果允许重偏向,那么将mark word中的Thread ID 重新置0;
  //                 如果不允许重偏向,那么将mark work设置为无锁状态,即最后两位为01

  // cached_monitor_info 是该线程拥有的锁对象的信息,按照从加锁顺序的逆序排列
  GrowableArray<MonitorInfo*>* cached_monitor_info =
                get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  for (int i = 0; i < cached_monitor_info->length(); i++) {
    MonitorInfo