第三章 当前Hotspot实现
3.1 Java对象和锁
在Hotspot虚拟机中,每个对象都包含一个由两个字长组成的对象头. 其中一个字用来识别对象的类型,另外一个称作mark word的字用于哈希码的计算,同步和垃圾回收. 通过最后两位mark word中可存储不同的信息. 这个字就是用于偏向,轻量级和重量级锁.
图3.1和3.2分别描述了32位和64位架构下的mark word布局. 由于哈希码的实现特性,哈希字段最多占用31位,因此在64位系统中,mark word留下了一些无用位. Age和CMS用于GC,它们与锁的实现无关.
图3.1 展示了在Hotspot 64位下markword的不同状态和布局
图3.2 展示了在Hotspot 32位下markword的不同状态和布局
3.2 轻量级锁
轻量级锁在Hotspot中使用显示头(displaced header)来实现,就是所谓的栈锁(stack locks)[8]. 当某一线程获取锁时,它会拷贝mark word到自己的锁记录中, 使用CAS来将对象头设置为轻量级锁状态,并在mark word中保留指向锁记录的指针. 最低的两位将会被设置成00以表明它已是轻量级锁,其余位则存储为指向原始mark word的指针[9]. 图3.3展示了当锁变为轻量级时对象和锁记录的状态,当递归获取时,该线程发现指针已经指向线程栈的某一个位置. 此时指向的锁记录被设置成NULL(0),表明它是递归的. 解锁时会检查锁记录,如果是空直接退出,因为锁被当前线程递归访问[10].
译者注 [8]: 这一段文字信息量很大,而且对于同一个事物使用了不同的术语来表达,稍有不慎就会导致理解混乱. 首先解释一下栈锁,我们知道线程内部有个栈结构, 锁记录就是存储在栈中,而轻量级锁就是利用存储在栈中的锁记录来实现,所以轻量级锁也称为栈锁.
译者注 [9]: 这两句话表明了从无锁到轻量级锁的发生的细节,简单来说有三个动作: 1. 将对象头中的
mark word拷贝到线程的锁记录中; 2. 使用CAS将mark word中的最后两位设置为00; 3. 使用CAS将mark word中的其他位设置为指向对应锁记录的指针;
译者注 [10]: 当持有偏向锁的线程以递归的方式再次尝试获取时, 发现指针已经指向了它内部的某一块栈地址(锁记录所在地址),于是就把该指针置为空. 也就是把
mark word中除了最后两位都置为0(此时后两位也是0,表示轻量级锁状态). 这样当该线程要释放锁时,无需前面提到的递归次数就直接可以释放了.
图3.3 展示了轻量级锁对象
当显式地解释执行或隐式地编译时,锁记录被分配到栈上. 锁记录包含了displaced mark word的位置以及指向锁定对象的指针. 当分配锁记录时,由解释器或编译器来初始化对象指针, 当升级为轻量级或重量级锁时初始化diplaced mark word.