锁在Java虚拟机中的实现和优化

理解锁在Java虚拟机中的实现和优化之前,我们需要知道对象头和锁的关系。
在Java虚拟机的实现中每个对象都有一个对象头,用于保存对象的系统信息。对象头中有一个称为Mark Word的部分,
它是实现锁的关键,在32位系统中,Mark Word为32个比特位,64位系统中,为64个比特位。它是一个多功能的数据区,
可以存放对象的哈希值,对象的年龄,锁的指针等信息,一个对象是否占用锁,占用哪个锁,就记录在这个Mark Word中。
以32位系统为例,普通对象的对象头如下所示

hash:25 ----------------->| age:4   biased_lock:1 lock:2

它表示Mark Word中有25个比特位表示对象的哈希值,4个比特位表示对象年龄,1个比特位表示是否为“偏向锁”,2个比特位表示锁的信息。

偏向锁

偏向锁是JDK 1.6 提出的一种锁优化方式。其核心思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。
也就说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,无需进行相关的同步操作,从而节省了操作时间。
如果在此之前有其他线程进行了锁请求,则锁退出偏向模式。
在JVM中使用-XX:+UseBiasedLocking可以设置启用偏向锁。

当锁对象处于偏向模式时,对象头会记录获取锁的线程

[JavaThread* | epoch | age | 1 | 10]

前23位表示持有偏向锁的线程,后续2位表示偏向锁的时间戳,4个比特位表示对象年龄,
年龄后1位固定为1, 表示偏向锁,最后2位表示可偏向/未锁定。

这样,当该线程再次尝试获得锁时,通过Mark Word的线程信息就可以判断当前线程是否持有偏向锁。
偏向锁在锁竞争激烈的场合没有太强的优化效果,因为大量的竞争会导致持有锁的线程不停地切换,锁也很难一直保持在偏向模式,
此时,使用锁偏向不仅得不到性能的优化,反而有可能降低系统性能。

轻量级锁

如果偏向锁失败,Java虚拟机会让线程申请轻量级锁。
轻量级锁在虚拟机内部,使用一个称谓BasicObjectLock的对象实现,这个对象内部由一个BasicLock对象和一个持有该锁的Java对象指针组成。
BasicObjectLock对象放置在Java栈的栈帧中。在BasicLock对象内部还维护者displaced_header字段,他用于备份对象头部的Mark Word。
当一个线程持有一个对象的锁时,对象头部Mark Word如下:

[prt         | 00] locked

末尾两位比特为00,整个Mark Word为指向BasicLock对象的指针。
由于BasicObjectLock对象在线程栈中,因此该指针必然指向持有该锁的线程栈空间。
当需要判断某一线程是否持有该对象锁时,也只需简单的判断对象头的指针是否在当前线程的栈地址范围即可。
同时,BasicLock对象的displaced_header字段,备份了元对象的Mark Word内存。BasicObjectLock对象的obj字段则指向该对象。

在虚拟机的实现中,有关轻量级加锁的代码实现可读性较好,这里给出其核心实现代码:

markOop mark = obj->mark();
lock->set_displaced_header(mark)
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, _addr(), mark)){
   TEVENT (slow_enter: release stacklock);
   return; 
}

首先, BasicLock通过set_displaced_header()方法备份了元对象的Mark Word。 接着使用CAS操作,尝试将BasicLock的地址复制到对象头的Mark Word。
如果复制成功,那么加锁成功,否则认为加锁失败。如果加锁失败,那么轻量级锁就有可能被膨胀为重量级锁。

锁膨胀

当轻量级锁失败,虚拟机就会使用重量级锁。在使用重量级锁时,对象的Mark Word如下:

[prt         | 10] monitor

末尾的2比特标记位被置为10。整个Mark Word表示指向monitor对象的指针。
在轻量级锁处理失败后,虚拟机会执行以下操作:

lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj()) -enter(THREAD);

第一步是废弃前面BasicLock备份的对象头信息,第二步则正式启用重量级锁。启用过程分为两步:

  • 通过inflate方法进行锁膨胀,其目的是获得对象的ObjectMonitor
    然后使用enter()方法尝试进入该锁
  • 在enter()方法的调用中,线程很可能会在操作系统层面被挂起。如果这样,线程间切换和调度的成本就会比较高。

自旋锁

锁膨胀后,进入ObjectMonitor的enter(),线程很可能会在操作系统层面被挂起,这样线程上下文切换的性能损失就比较大。
因此,在锁膨胀后,虚拟机会做最后的争取,希望线程可以尽快进入临界区而避免被操作系统挂起。
一种较为有效的手段就是使用自旋锁。
自旋锁可以使线程在没有取得锁时,不被挂起,而转而去执行一个空循环(即所谓的自旋),
在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。

使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。
因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,
但对于锁竞争激烈,单线程锁占用时间长的并发程序,自旋锁在自旋等待后,往往依然无法获得对应的锁,
不仅仅白白浪费了CPU时间,最终还是免不了执行被挂起的操作,反而浪费了系统资源。

在JDK 1.6 中,Java虚拟机提供-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁的等待次数。
在JDK 1.7中,自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁。自旋锁总是会执行,自旋次数也由虚拟机自行调整。

锁消除

锁消除是Java虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。通过锁消除,可以节省毫无意义的请求锁时间。

锁消除主要针对在Java开发过程中,开发人员必然会使用一些JDK内置的API, 必然StringBuffer, Vector等。
这些常用的工具类可能会被大面积使用。虽然这些工具类本上可能有对应的非线程安全版本,必然StringBuilder, ArrayList等,
但是开发人员也很可能在完全没有多少线程竞争的场合中使用它们。

在这种情况下,这些工具类内部的同步方法就是不必要的。虚拟机可以在运行时,基于逃逸分析技术,捕获这些不可能存在竞争,却有申请锁的代码段,
并消除这些不必要的锁,从而提高系统性能。

逃逸分析和锁消除分别可以使用参数-XX:+DoEscapeAnalysis和-XX:+EliminateLocks开启(锁消除必须工作在-server模式下

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容