鎖在Java虛擬機中的實現(xiàn)和優(yōu)化

理解鎖在Java虛擬機中的實現(xiàn)和優(yōu)化之前院崇,我們需要知道對象頭和鎖的關(guān)系嘁信。
在Java虛擬機的實現(xiàn)中每個對象都有一個對象頭尤蛮,用于保存對象的系統(tǒng)信息呐馆。對象頭中有一個稱為Mark Word的部分狞尔,
它是實現(xiàn)鎖的關(guān)鍵丛版,在32位系統(tǒng)中,Mark Word為32個比特位偏序,64位系統(tǒng)中页畦,為64個比特位。它是一個多功能的數(shù)據(jù)區(qū)研儒,
可以存放對象的哈希值豫缨,對象的年齡独令,鎖的指針等信息,一個對象是否占用鎖好芭,占用哪個鎖燃箭,就記錄在這個Mark Word中。
以32位系統(tǒng)為例舍败,普通對象的對象頭如下所示

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

它表示Mark Word中有25個比特位表示對象的哈希值招狸,4個比特位表示對象年齡,1個比特位表示是否為“偏向鎖”邻薯,2個比特位表示鎖的信息裙戏。

偏向鎖

偏向鎖是JDK 1.6 提出的一種鎖優(yōu)化方式。其核心思想是厕诡,如果程序沒有競爭累榜,則取消之前已經(jīng)取得鎖的線程同步操作。
也就說灵嫌,若某一鎖被線程獲取后壹罚,便進入偏向模式,當(dāng)線程再次請求這個鎖時醒第,無需進行相關(guān)的同步操作渔嚷,從而節(jié)省了操作時間。
如果在此之前有其他線程進行了鎖請求稠曼,則鎖退出偏向模式形病。
在JVM中使用-XX:+UseBiasedLocking可以設(shè)置啟用偏向鎖。

當(dāng)鎖對象處于偏向模式時霞幅,對象頭會記錄獲取鎖的線程

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

前23位表示持有偏向鎖的線程漠吻,后續(xù)2位表示偏向鎖的時間戳,4個比特位表示對象年齡司恳,
年齡后1位固定為1途乃, 表示偏向鎖,最后2位表示可偏向/未鎖定扔傅。

這樣耍共,當(dāng)該線程再次嘗試獲得鎖時,通過Mark Word的線程信息就可以判斷當(dāng)前線程是否持有偏向鎖猎塞。
偏向鎖在鎖競爭激烈的場合沒有太強的優(yōu)化效果试读,因為大量的競爭會導(dǎo)致持有鎖的線程不停地切換,鎖也很難一直保持在偏向模式荠耽,
此時钩骇,使用鎖偏向不僅得不到性能的優(yōu)化,反而有可能降低系統(tǒng)性能。

輕量級鎖

如果偏向鎖失敗倘屹,Java虛擬機會讓線程申請輕量級鎖银亲。
輕量級鎖在虛擬機內(nèi)部,使用一個稱謂BasicObjectLock的對象實現(xiàn)纽匙,這個對象內(nèi)部由一個BasicLock對象和一個持有該鎖的Java對象指針組成务蝠。
BasicObjectLock對象放置在Java棧的棧幀中。在BasicLock對象內(nèi)部還維護者displaced_header字段哄辣,他用于備份對象頭部的Mark Word请梢。
當(dāng)一個線程持有一個對象的鎖時,對象頭部Mark Word如下:

[prt         | 00] locked

末尾兩位比特為00力穗,整個Mark Word為指向BasicLock對象的指針。
由于BasicObjectLock對象在線程棧中气嫁,因此該指針必然指向持有該鎖的線程椀贝埃空間。
當(dāng)需要判斷某一線程是否持有該對象鎖時寸宵,也只需簡單的判斷對象頭的指針是否在當(dāng)前線程的棧地址范圍即可崖面。
同時,BasicLock對象的displaced_header字段梯影,備份了元對象的Mark Word內(nèi)存巫员。BasicObjectLock對象的obj字段則指向該對象。

在虛擬機的實現(xiàn)中甲棍,有關(guān)輕量級加鎖的代碼實現(xiàn)可讀性較好简识,這里給出其核心實現(xiàn)代碼:

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的地址復(fù)制到對象頭的Mark Word。
如果復(fù)制成功陪白,那么加鎖成功颈走,否則認為加鎖失敗。如果加鎖失敗咱士,那么輕量級鎖就有可能被膨脹為重量級鎖立由。

鎖膨脹

當(dāng)輕量級鎖失敗,虛擬機就會使用重量級鎖序厉。在使用重量級鎖時锐膜,對象的Mark Word如下:

[prt         | 10] monitor

末尾的2比特標(biāo)記位被置為10。整個Mark Word表示指向monitor對象的指針脂矫。
在輕量級鎖處理失敗后枣耀,虛擬機會執(zhí)行以下操作:

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

第一步是廢棄前面BasicLock備份的對象頭信息,第二步則正式啟用重量級鎖。啟用過程分為兩步:

  • 通過inflate方法進行鎖膨脹捞奕,其目的是獲得對象的ObjectMonitor
    然后使用enter()方法嘗試進入該鎖
  • 在enter()方法的調(diào)用中牺堰,線程很可能會在操作系統(tǒng)層面被掛起。如果這樣颅围,線程間切換和調(diào)度的成本就會比較高伟葫。

自旋鎖

鎖膨脹后,進入ObjectMonitor的enter()院促,線程很可能會在操作系統(tǒng)層面被掛起筏养,這樣線程上下文切換的性能損失就比較大。
因此常拓,在鎖膨脹后渐溶,虛擬機會做最后的爭取,希望線程可以盡快進入臨界區(qū)而避免被操作系統(tǒng)掛起弄抬。
一種較為有效的手段就是使用自旋鎖茎辐。
自旋鎖可以使線程在沒有取得鎖時,不被掛起掂恕,而轉(zhuǎn)而去執(zhí)行一個空循環(huán)(即所謂的自旋)拖陆,
在若干個空循環(huán)后,線程如果可以獲得鎖懊亡,則繼續(xù)執(zhí)行依啰。若線程依然不能獲得鎖烟具,才會被掛起斤程。

使用自旋鎖后,線程被掛起的幾率相對減少强霎,線程執(zhí)行的連貫性相對加強艰争。
因此坏瞄,對于那些鎖競爭不是很激烈,鎖占用時間很短的并發(fā)線程甩卓,具有一定的積極意義鸠匀,
但對于鎖競爭激烈,單線程鎖占用時間長的并發(fā)程序逾柿,自旋鎖在自旋等待后缀棍,往往依然無法獲得對應(yīng)的鎖,
不僅僅白白浪費了CPU時間机错,最終還是免不了執(zhí)行被掛起的操作爬范,反而浪費了系統(tǒng)資源。

在JDK 1.6 中弱匪,Java虛擬機提供-XX:+UseSpinning參數(shù)來開啟自旋鎖青瀑,使用-XX:PreBlockSpin參數(shù)來設(shè)置自旋鎖的等待次數(shù)。
在JDK 1.7中,自旋鎖的參數(shù)被取消斥难,虛擬機不再支持由用戶配置自旋鎖枝嘶。自旋鎖總是會執(zhí)行,自旋次數(shù)也由虛擬機自行調(diào)整哑诊。

鎖消除

鎖消除是Java虛擬機在JIT編譯時群扶,通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖镀裤。通過鎖消除竞阐,可以節(jié)省毫無意義的請求鎖時間。

鎖消除主要針對在Java開發(fā)過程中暑劝,開發(fā)人員必然會使用一些JDK內(nèi)置的API, 必然StringBuffer, Vector等骆莹。
這些常用的工具類可能會被大面積使用。雖然這些工具類本上可能有對應(yīng)的非線程安全版本担猛,必然StringBuilder, ArrayList等汪疮,
但是開發(fā)人員也很可能在完全沒有多少線程競爭的場合中使用它們。

在這種情況下毁习,這些工具類內(nèi)部的同步方法就是不必要的。虛擬機可以在運行時卖丸,基于逃逸分析技術(shù)纺且,捕獲這些不可能存在競爭,卻有申請鎖的代碼段稍浆,
并消除這些不必要的鎖载碌,從而提高系統(tǒng)性能。

逃逸分析和鎖消除分別可以使用參數(shù)-XX:+DoEscapeAnalysis和-XX:+EliminateLocks開啟(鎖消除必須工作在-server模式下

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衅枫,一起剝皮案震驚了整個濱河市嫁艇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弦撩,老刑警劉巖步咪,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異益楼,居然都是意外死亡猾漫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門感凤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悯周,“玉大人,你說我怎么就攤上這事陪竿∏菀恚” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闰挡。 經(jīng)常有香客問我锐墙,道長,這世上最難降的妖魔是什么解总? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任贮匕,我火速辦了婚禮,結(jié)果婚禮上花枫,老公的妹妹穿的比我還像新娘刻盐。我一直安慰自己,他們只是感情好劳翰,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布敦锌。 她就那樣靜靜地躺著,像睡著了一般佳簸。 火紅的嫁衣襯著肌膚如雪乙墙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天生均,我揣著相機與錄音听想,去河邊找鬼。 笑死马胧,一個胖子當(dāng)著我的面吹牛汉买,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播佩脊,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛙粘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了威彰?” 一聲冷哼從身側(cè)響起出牧,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歇盼,沒想到半個月后舔痕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡旺遮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年赵讯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耿眉。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡边翼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸣剪,到底是詐尸還是另有隱情组底,我是刑警寧澤丈积,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站债鸡,受9級特大地震影響江滨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厌均,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一唬滑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棺弊,春花似錦晶密、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侈净,卻和暖如春尊勿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畜侦。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工元扔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旋膳。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓摇展,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溺忧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容