JVM鎖簡介:偏向鎖、輕量級鎖和重量級鎖


轉自:https://www.aimoon.site/blog/2018/05/21/biased-locking/

 比較復雜,簡略見另一篇:https://www.cnblogs.com/twoheads/p/10148598.html

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* mon_info = cached_monitor_info->at(i); if (mon_info->owner() == obj) { // Assume recursive case and fix up highest lock later markOop mark = markOopDesc::encode((BasicLock*) NULL); highest_lock = mon_info->lock(); highest_lock->set_displaced_header(mark); } } if (highest_lock != NULL) { // 線程擁有鎖 // Fix up highest lock to contain displaced header and point // object at it highest_lock->set_displaced_header(unbiased_prototype); // Reset object header to point to displaced mark. // Must release storing the lock address for platforms without TSO // ordering (e.g. ppc). obj->release_set_mark(markOopDesc::encode(highest_lock)); } else { // 線程不再擁有鎖 if (allow_rebias) { obj->set_mark(biased_prototype); } else { obj->set_mark(unbiased_prototype); } } return BiasedLocking::BIAS_REVOKED; } 

小結: 撤銷偏向的操作需要在全局檢查點執行。我們假設線程A曾經擁有鎖(不確定是否釋放鎖), 線程B來競爭鎖對象,如果當線程A不在擁有鎖時或者死亡時,線程B直接去嘗試獲得鎖(根據是否 允許重偏向(rebiasing),獲得偏向鎖或者輕量級鎖);如果線程A仍然擁有鎖,那么鎖 升級為輕量級鎖,線程B自旋請求獲得鎖。整個撤銷偏向(revoke bias)的偽代碼如下所示:

// 撤銷流程的偽代碼,在全局檢查點執行該操作
if mark word 存儲的不是可偏向狀態: return; // 如果不是偏向鎖,那么沒有撤銷偏向的必要 else: if Thread ID 指向的線程不存活: if 允許重偏向: 退回可偏向但未偏向的狀態 // Thread ID為0 else: 偏向撤銷,變為無鎖狀態 else: if Thread ID 指向的線程,仍然擁有鎖: 升級為輕量級鎖,將mark word復制到線程棧中,然后stack pointer指向最老的相關鎖記錄 else: if 允許重偏向: 退回可偏向但未偏向的狀態 // Thread ID為0 else: 偏向撤銷,變為無鎖狀態 

下圖中的線程1演示了偏向鎖初始化的流程,線程2演示了偏向鎖撤銷的流程:圖片來源:並發編程網-偏向鎖的獲得和偏向流程

圖3   偏向鎖的獲得和偏向流程

1.3 epoch字段解釋

上面介紹了那么多,還有一小部分的內容沒有介紹,就是Mark Word中的epoch字段,epoch 主要是用於輔助批量重偏向(bulk rebiasing)和批量偏向撤銷(bulk revocation)操作。 下面先講一下什么叫做批量重偏向和批量偏向撤銷。

First, there are certain objects for which biased locking is obviously unprofitable, such as producer-consumer queues where two or more threads are involved. Such objects necessarily have lock contention, and many such objects may be allocated during a program’s execution. It would be ideal to be able to identify such objects and disable biased locking only for them.

————引自論文《Eliminating Synchronization-Related Atomic Operations
with Biased Locking and Bulk Rebiasing》第3頁第4部分

意思就是說,某些操作,例如生產者和消費者隊列,線程之間的競爭是不可避免的,這時候就不適合使用偏向鎖。 我們需要識別這些情況,然后有選擇的禁用偏向鎖。此時顯然不是只有一個鎖,而是一個類或者該類的多個對象 作為鎖,我們需要對這個類的所有對象都撤銷偏向鎖,這個優化操作,就叫做批量撤銷偏向(bulk revocation`)

換句話說,就是在某一階段,某個類的對象頻繁發生偏向撤銷的操作,當次數超過預定的閾值(threshold) 時,就批量撤銷該類所有對象的偏向。

Second, there are situations in which the ability to rebias a set of objects to another thread is profitable, in particular when one thread allocates many objects and performs an initial synchronization operation on each, but another thread performs subsequent work on them.

————引自論文《Eliminating Synchronization-Related Atomic Operations
with Biased Locking and Bulk Rebiasing》第3-4頁第4部分

意思是說,在某些情況下,例如一個線程先分配了一定數量的對象(屬於同一類型),然后對這些對象 執行同步操作,在該線程操作完成后,另一個線程接着執行同步操作,但是這兩個線程的操作是順序 執行的,不交互,我們就可以將前一個線程擁有的偏向重偏向到后一個線程,這個優化操作,就叫做 批量重偏向(bulk rebiasing)。

HotSpot通過Heuristics這個類來統計某一時間段內某個類對象的撤銷偏向和重偏向的次數。

那么如何對某一特定數據類型的所有對象進行統計?HotSpot剛開始采用的方案是:遍歷對象堆。 但是當堆變得比較大的時候,其擴展性就比較差,為了解決這個問題,引入了epoch的概念, 用epoch這個字段來表示偏向是否有效。

每一個可偏向的數據類型(類)都有自己響應的epoch值。注意,這個epoch number屬於類。 此時,一個線程擁有偏向鎖,指的是:對象頭中的Mark Word的Thread ID字段指向該想線程, 並且對象實例的epoch字段和類的epoch字段值相同,如果不相同,可以認為該對象處於可偏向但 未偏向的狀態。這樣就不需要遍歷整個對象堆,提高了效率。

注意:批量重偏向的操作,仍然需要在全局檢查點(global safepoint)執行。 在全局檢查點, 批量重偏向的額外操作如下:

  1. 將類中的epoch number加1。
  2. 然后,掃描所有的線程棧,定位所有仍然擁有鎖的類的對象,然后將這些對象的epoch字段值, 更新為類的epoch字段值。基於Heuristics方法的考慮,這些對象的偏向可能被撤銷。

另外,在引入epoch字段之后,獲取偏向鎖的流程如下面的偽代碼所示:

// Biased locking acquisition supporting epoch-based bulk rebiasing and revocation void lock (Object* obj, Thread* t) { int lw = obj->lock_word; if (lock_state(lw) == Biased && biasable(lw) == obj->class->biasable && bias_epoch(lw) == obj->class->epoch) { if (lock_or_bias_owner == t->id) { // current thread is the bias owner return; } else { // need to revoke the object bias revoke_bias(obj, t); } } else { // normal locking/unlocking protocal, // possibly with bias acquisition. { } 

代碼來源:引自論文Eliminating Synchronization-RelatedAtomic Operations with Biased Locking and Bulk Rebiasing第5頁 listing 1 和 listing 2.

2. 輕量級鎖

輕量級鎖,也是JDK 1.6加入的新機制,之所以成為“輕量級”,是因為它不是使用操作系統互斥量來實現鎖, 而是通過CAS操作,來實現鎖。當線程獲得輕量級鎖后,可以再次進入鎖,即鎖是可重入(Reentrance Lock)的。

在輕量級鎖的加鎖階段,如果線程發現對象頭中Mark Word已經存在指向自己棧幀的指針,即線程 已經獲得輕量級鎖,那么只需要將0存儲在自己的棧幀中(此過程稱為遞歸加鎖);在解鎖的時候,如果發現鎖記錄的內容為0, 那么只需要移除棧幀中的鎖記錄即可,而不需要更新Mark Word。

2.1 輕量級鎖的加鎖和解鎖過程

輕量級鎖的加鎖流程

線程在執行同步塊之前,JVM會先在當前的線程的棧幀中創建所記錄的空間,用於存儲對象頭中的 Mark Word的拷貝(官方稱之為Displaced Mark Word),如圖4所示。圖片來源:PPT The Hotspot Java Virtual Machine第 29 頁

圖4   輕量級鎖CAS操作之前堆棧和對象的狀態

然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄(Lock Record)的指針, 如圖5所示。如果成功,當前線程獲得輕量級鎖,如果失敗,虛擬機先檢查當前對象頭的Mark Word 是否指向當前線程的棧幀,如果指向,則說明當前線程已經擁有這個對象的鎖,則可以直接進入同步塊 執行操作,否則表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。當競爭線程的自旋次數 達到界限值(threshold),輕量級鎖將會膨脹為重量級鎖。

: 輕量級鎖的獲取過程請查看代碼synchronizer.cpp中的slow_enter方法

圖片來源:PPT The Hotspot Java Virtual Machine第 30 頁

圖5   輕量級鎖CAS操作之后堆棧和對象的狀態

輕量級鎖的解鎖流程

輕量級鎖解鎖時,如果對象的Mark Word仍然指向着線程的鎖記錄,會使用CAS操作, 將Dispalced Mark Word替換到對象頭,如果成功,則表示沒有競爭發生。如果失敗, 表示當前鎖存在鎖競爭,鎖就會膨脹為重量級鎖。

: 輕量級鎖的獲取過程請查看代碼synchronizer.cpp中的fast_exit方法, 由synchronizer.cpp中的slow_exit方法調用。

2.2 輕量級鎖膨脹為重量級鎖

下面結合代碼(有縮減)分析一下輕量級鎖膨脹為重量級鎖的過程。詳細代碼請查看 synchronizer.cpp中的inflate方法。返回的是一個monitor對象。

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) { for (;;) { const markOop mark = object->mark() ; // The mark can be in one of the following states: // * Inflated - 直接返回 // * Stack-locked - 需要膨脹為重量級鎖 // * INFLATING - 需要等待其他線程的鎖膨脹操作完成 // * Neutral - 無鎖狀態,膨脹為重量級鎖 // * BIASED - 非法狀態,不可能存在 // CASE: inflated // 如果已經膨脹為重量級鎖,那么直接返回 if (mark->has_monitor()) { ObjectMonitor * inf = mark->monitor(); return inf; } // CASE: inflation in progress - inflating over a stack-lock. // 這個狀態很快就消失(transient) // 有其他線程正在將輕量級鎖轉換為重量級鎖,只有這個線程可以完成膨脹操作, // 其他線程必須等待膨脹完成。雖然是自旋操作,但不會一直占用cpu資源, // 每隔一段時間會通過os::NakedYield方法放棄cpu資源,或通過park方法掛起 if (mark == markOopDesc::INFLATING()) { TEVENT (Inflate: spin while INFLATING) ; ReadStableMark(object) ; // 自旋,等待膨脹完成 continue ; } // CASE: stack-locked // 可能該線程或者其他線程擁有輕量級鎖,需要膨脹為重量級鎖 if (mark->has_locker()) { ObjectMonitor * m = omAlloc (Self) ; // Optimistically prepare the objectmonitor - anticipate successful CAS // We do this before the CAS in order to minimize the length of time // in which INFLATING appears in the mark. m->Recycle(); m->_Responsible = NULL ; m->OwnerIsThread = 0 ; m->_recursions = 0 ; m->_SpinDuration = ObjectMonitor::Knob_SpinLimit; markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; if (cmp != mark) { omRelease (Self, m, true) ; continue ; // Interference -- just retry } // 此時已經成功將mark word的狀態改為INFLATING狀態 // 從鎖擁有者的棧幀中獲取displaced mark word,然后設置monitor的各個字段,並返回 markOop dmw = mark->displaced_mark_helper() ; m->set_header(dmw) ; m->set_owner(mark->locker()); m->set_object(object); // Must preserve store ordering. The monitor state must // be stable at the time of publishing the monitor address. guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ; object->release_set_mark(markOopDesc::encode(m)); // Hopefully the performance counters are allocated on distinct cache lines // to avoid false sharing on MP systems ... if (ObjectMonitor::_sync_Inflations != NULL) { ObjectMonitor::_sync_Inflations->inc() ; } TEVENT(Inflate: overwrite stacklock); return m ; } // CASE: neutral // 將鎖膨脹為重量級鎖 ObjectMonitor * m = omAlloc (Self) ; // prepare m for installation - set monitor to initial state m->Recycle(); m->set_header(mark); m->set_owner(NULL); // 沒有所有者 m->set_object(object); m->OwnerIsThread = 1 ; m->_recursions = 0 ; m->_Responsible = NULL ; m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) { m->set_object (NULL) ; m->set_owner (NULL) ; m->OwnerIsThread = 0 ; m->Recycle() ; omRelease (Self, m, true) ; m = NULL ; continue ; // interference - the markword changed - just retry. // The state-transitions are one-way, so there's no chance of // live-lock -- "Inflated" is an absorbing state. } // Hopefully the performance counters are allocated on distinct // cache lines to avoid false sharing on MP systems ... if (ObjectMonitor::_sync_Inflations != NULL){ ObjectMonitor::_sync_Inflations->inc(); } TEVENT(Inflate: overwrite neutral); return m; } } 

鎖膨脹要分為多種情況進行分析:

  • Inflated:說明已經是膨脹鎖了,直接返回。
  • Inflating:說明其他線程在執行膨脹鎖的操作,自旋等待膨脹完成,該狀態是為了保證 只有一個線程執行膨脹鎖的操作。
  • Stack-locked:由輕量級鎖膨脹為重量級鎖,首先通過CAS將Mark Word置0,即變為INFLATING 狀態,然后先生成一個monitor,然后通過CAS將Mark Word指向monitor
  • Neutral:無鎖狀態,膨脹為重量級鎖,先生成一個monitor,然后通過CAS將Mark Word指向monitor

: 鎖膨脹完成后,返回對應的monitor,並且僅僅是返回monitor,不涉及線程競爭加鎖和解鎖。 真正的加鎖操作是enter,解鎖操作是exit,加鎖和釋放的代碼在objectMonitor.cpp中。圖片來源:並發編程網-輕量級鎖及膨脹流程圖

圖6   輕量級鎖及膨脹流程圖

3. 重量級鎖

重量級鎖(heavy weight lock),是使用操作系統互斥量(mutex)來實現的傳統鎖。 當所有對鎖的優化都失效時,將退回到重量級鎖。它與輕量級鎖不同競爭的線程不再通過自旋來競爭線程, 而是直接進入堵塞狀態,此時不消耗CPU,然后等擁有鎖的線程釋放鎖后,喚醒堵塞的線程, 然后線程再次競爭鎖。但是注意,當鎖膨脹(inflate)為重量鎖時,就不能再退回到輕量級鎖。

重量級鎖的加鎖和釋放的代碼在objectMonitor.cpp中。 對於重量級鎖的加鎖和釋放過程,我沒有細看,因為涉及到底層的操作系統,看起來難道比較大。

4. 鎖的對比

下面對偏向鎖、輕量級鎖和重量級鎖進行比較:

表3   各種鎖的優缺點及適用場景

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,與執行非同步方法僅存在納秒級的差距 如果線程間存在競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個線程訪問同步塊的情況
輕量級鎖 競爭的線程不會堵塞,提高了程序的響應速度 始終得不到鎖的線程,使用自旋會消耗CPU 追求響應時間,同步塊執行速度非常塊,只有兩個線程競爭鎖
重量級鎖 線程競爭不使用自旋,不會消耗CPU 線程堵塞,響應時間緩慢 追求吞吐量,同步塊執行速度比較慢,競爭鎖的線程大於2個

表格來源:《Java並發編程的藝術》第16頁

hashcode()方法對偏向鎖的影響

hashcode()方法Object類中,是一個native方法,用於計算對象的哈希值,可以和equla() 方法配合來比較對象是否相等。這里插一句,不同的對象可能會生成相同的hashcode值,所以不能 只根據hashcode值判斷兩個對象是否是同一對象,但是如果兩個對象的hashcode值不等,則必定是兩個不同的對象。 也就是說在比較對象是否為同一對象的時候,先判斷兩個對象的hashcode值是否真正相等, 如果不相等,那么肯定不是同一對象。如果相等,然后使用equals方法,如果相等就是同一個對象。

另外,我們通常認為該方法返回的是對象的內存地址,其實這種說法是不太准確的。例如,在 JDK 1.8中,這種方法是完全錯誤的。

synchroizer.cpp中的get_next_hash()方法中,介紹了幾種計算hashcode的算法,如下所示(有刪減):

// hashCode() generation static inline intptr_t get_next_hash(Thread * Self, oop obj) { intptr_t value = 0 ; if (hashCode == 0) { // 系統生成的隨機數 value = os::random() ; } else if (hashCode == 1) { // 對對象的內存地址進行二次計算 // This can be useful in some of the 1-0 synchronization schemes. intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ; value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ; } else if (hashCode == 2) { value = 1 ; // 用於敏感性測試 } else if (hashCode == 3) { value = ++GVars.hcSequence ; // 自增的序列 } else if (hashCode == 4) { value = cast_from_oop<intptr_t>(obj) ; // 對象的內存地址,轉為int } else { // hashCode == 5 // Marsaglia's xor-shift scheme with thread-specific state unsigned t = Self->_hashStateX ; t ^= (t << 11) ; Self->_hashStateX = Self->_hashStateY ; Self->_hashStateY = Self->_hashStateZ ; Self->_hashStateZ = Self->_hashStateW ; unsigned v = Self->_hashStateW ; v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ; Self->_hashStateW = v ; value = v ; } value &= markOopDesc::hash_mask; if (value == 0) value = 0xBAD ; assert (value != markOopDesc::no_hash, "invariant") ; TEVENT (hashCode: GENERATE) ; return value; } 

小結: 根據上面的代碼,可以知道,有如下幾種算法:

  • hashCode == 0: 系統生成的隨機數
  • hashCode == 1: 對對象的內存地址進行二次計算
  • hashCode == 2: 硬編碼1 (用於敏感性測試)
  • hashCode == 3: 一個自增的序列
  • hashCode == 4: 對象的內存地址,轉為int
  • hashCode == 5: Marsaglia’s xor-shift scheme with thread-specific state

global.hpp中,我們可以看出,JDK 1.8使用的計算hashcode的算法為5, 也就是Marsaglia’s xor-shift scheme with thread-specific state (原諒我,不知道怎么翻譯-_-//),所以hashcode的值與對象的內存地址,沒有什么關系。

  product(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm") 

:可以使用參數-XX:hashCode=[0-5]來改變默認的算法。

下面結合代碼(有刪減,詳細代碼請查看synchroizer.cpp中的FastHashCode()方法), 分析一下,計算hashcode對鎖狀態的影響:

intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) { if (UseBiasedLocking) { // 如果使用偏向鎖,那么撤銷偏向鎖 if (obj->mark()->has_bias_pattern()) { // Box and unbox the raw reference just in case we cause a STW safepoint. // 導致STW發生,進入全局檢查點 Handle hobj (Self, obj) ; BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current()); obj = hobj() ; } } // hashCode() is a heap mutator ... ObjectMonitor* monitor = NULL; markOop temp, test; intptr_t hash; markOop mark = ReadStableMark (obj); // 此時,已經不可能是偏向鎖狀態,那么還有3種狀態:無鎖,輕量級鎖和重量級鎖 if (mark->is_neutral()) { // 此時是無鎖狀態,如果有hash code,那么直接返回,否則進行計算 hash = mark->hash(); // this is a normal header if (hash) { // if it has hash, just return it return hash; } hash = get_next_hash(Self, obj); // allocate a new hash code temp = mark->copy_set_hash(hash); // merge the hash code into header // 將計算的hash code使用CAS操作,拷貝到mark word中 test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark); if (test == mark) { return hash; } // 如果CAS操作失敗,那么需要膨脹為重量級鎖 } else if (mark->has_monitor()) { // 此時是重量級鎖 monitor = mark->monitor(); temp = monitor->header(); hash = temp->hash(); if (hash) { return hash; } // Skip to the following code to reduce code size } else if (Self->is_lock_owned((address)mark->locker())) { // 此時是輕量級鎖狀態,查看displaced mark word中是否含有hash code // 如果沒有,那么膨脹為重量級鎖 temp = mark->displaced_mark_helper(); hash = temp->hash(); // by current thread, check if the displaced if (hash) { // header contains hash code return hash; } } // 膨脹為重量值鎖,然后設置hash code monitor = ObjectSynchronizer::inflate(Self, obj); // 鎖膨脹 // Load displaced header and check it has hash code mark = monitor->header(); hash = mark->hash(); if (hash == 0) { hash = get_next_hash(Self, obj); temp = mark->copy_set_hash(hash); // merge hash code into header test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark); if (test != mark) { hash = test->hash(); } } // We finally get the hash return hash; } 

小結: 根據上面的代碼可以知道,在HotSpot, 調用hashCode(), 或者System.identityHashCode() 方法會導致對象撤銷偏向鎖。換句話說,如果在明顯存在競爭的情況下,例如生產者/消費者隊列, 可以通過簡單的調用hashCode()或者System.identityHashCode()方法來禁用偏向鎖。

參考文獻

  1. 深入理解java虛擬機, 周志明.
  2. Java並發編程的藝術, 方騰飛, 魏鵬, 程曉明.
  3. Kenneth Russell, David Detlefs. Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing
  4. Galo Navarro. How does the default hashCode() work?
  5. Paul Hohensee. The Hotspot Java Virtual Machine, p25-35.
  6. Thomas Kotzmann, Christian Wimmer. Synchronization and Object Locking
  7. Dave Dice. Biased Locking in HotSpot
  8. ross. Java並發之徹底搞懂偏向鎖升級為輕量級鎖
  9. OpenJDK 1.8 虛擬機部分源代碼:markOop.hpp、 biasedLocking.cpp、 basicLock.cpp、 synchronizer.cppobjectMonitor.cpp
  10. 占小狼.JVM源碼分析之synchronized實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM