CVM Object Allocation

原文鏈接:http://weblogs.java.net/blog/mlam/archive/2008/06/cvm_object_allo.html#more

 

CVM 對象分配

 

在之前的評論中,Jamsheed問道:

“在CDC中有快速鎖競爭情況下的垃圾收集調用(從我的理解來看就是輪詢不安全對象分配線程使之成爲垃圾收集安全的線程)。我的問題是爲什麼一定要等到達到一個安全點之後才進行垃圾回收的調用而不是在一個gc安全窗口中獲得一個堆的鎖(稍微修改一下gc安全窗口)。或者在每次迭代到達安全點之後輪詢嘗試獲得堆鎖。”

 

Jamsheed,我假定你是指那段要求所有線程達到GC安全狀態的分配的代碼。你可能認爲那是個相當慢的過程,應該有代價更低的替換代碼,而爲什麼還要那樣做?

 

下面就是理由…

 

一些背景…

對於那些不知道我們在討論什麼的人來說,這段代碼可以在多處被找到。其中一處在gc_common.c文件中的爲新對象分配內存的函數中。那裏有一處對微型鎖的測試。如果微型鎖現在並未被當前線程所持有,那麼分配的代碼就要求所有的線程達到一個GC安全狀態。也就是經常在GC討論中所指的“stop the world”。

 

在許多常見的GC算法中(如在CVM的GC中),GC需要掃描整個VM中的所有對象指針以確定哪些對象仍可觸及因而不應被垃圾收集。爲了做到這一點必須確保正在工作的所有線程不能在GC不知道的情況下隨便移動對象的指針。這些線程被稱爲mutator線程,因爲它們會mutate(也就是改變)線程中的指針的狀態。這是一個對mutation(變化)過於簡化的描述,但已足夠描述這一點了。

 

所謂GC安全狀態是指線程已約定不會改變任何對象指針。因此,當我們需要GC時,首先必須使所有線程達到一個GC安全狀態。當所有的線程都各自達到它們的GC安全狀態時,就稱爲到達了一個GC安全狀態。按照定義,它們不會再改變線程(至少它們不會妨礙GC)。所以,更準確地說,GC已經“stopped the world”…至少在以後被通知之前不做任何改變。線程仍可以運行並執行它們的工作就是不能改變對象的指針。如果線程要改變任何對象指針,它就會被阻塞知道GC允許它繼續執行。

 

那麼這與對象分配有什麼關係?

 

快速分配

在一個連續的空閒堆內存區域最快的分配對象的方法就是簡單地增大堆頂指針。本質上就是保存一個指向堆頂的指針。這個指針指向堆中已被分配的內存的最高地址。當需要一個新的分配時,簡單地將指針加上需要分配的內存的大小。指針之前的值就是新分配的內存的地址。

 

然而這隻有在僅一個線程執行所有的分配工作時纔可行。如果有多於一個線程,必須保證這些線程不會同時改變指針的值。爲做到這一點,CVM(在多數目標平臺上)使用了自旋鎖。自旋鎖的實現是通過使用一個原子交換指令。原子交換指令就是在檢測標誌值的同時鎖住標誌。

 

多數時刻,不同的線程並不同時在分配內存。因此,請求微型鎖標誌的線程一般都會成功獲得它,增大堆頂指針值,完成分配並釋放微型鎖標誌。

 

聽起來不錯,但當多個線程同時在堆上分配內存時該怎樣呢?

 

慢速分配

當多個線程同時競爭分配內存時,第二個線程,T2,嘗試獲取微型鎖標誌時就會被阻塞直到第一個線程釋放鎖標誌。注意:微型鎖標誌只是一個標誌域而不是一個互斥變量。所以並無與它相關的阻塞功能。期望的是T2被阻塞直到T1完成分配然後T2被喚醒並得到分配堆的控制權。

 

一種解決辦法是當T2請求時,“stop the world”,停止所有線程。在T2請求之後,它被阻塞,等待所有線程達到它們的GC安全狀態。同時,T1在非GC安全狀態下執行內存分配。最終,所有的線程達到它們各自的GC安全狀態,喚醒T2。此時,T2可以高枕無憂地執行它的內存分配而不必擔心來自其它線程的競爭,因爲它們都已經停止了。

 

總之:快速分配的情況僅可能發生在一個線程的非GC安全狀態時。如果T2提出一個“stop the world”使所有的線程進入GC安全狀態,那麼可以保證沒有其它線程同時在快速分配。且由於T2是唯一成功地請求了“stop the world”,沒有其它線程同時在請求“stop the world”。這確保了T2是進行慢速分配的唯一線程。

 

所以,“stop the world”請求是堆資源分配時線程同步的機制。

 

回到Jamsheed的問題上來…

 

爲什麼不使用一個互斥變量?

 

原因是相對自旋鎖來說,互斥變量是重量級的機制。如果僅是參考gc_common.c中分配的C代碼這還不是那麼明顯。然而,快速分配有一個用於JIT編譯後的彙編代碼。通過這個快速分配的版本可以編譯後代碼執行。如果使用C代碼編譯後的版本則會增加許多額外的代碼。JIT編譯後的代碼檢測自旋鎖時更爲容易,但它不能像C版本那樣使用系統的互斥變量(它是獨立於平臺實現的)。

 

因此,使用自旋鎖標誌而非真正意義上的互斥變量是爲了使JIT編譯後的代碼執行效率更高。正如之前指出的,多數情況下競爭不會發生,可以繼續使用快速分配。但是在少數情況下當競爭發生時,會切換到C代碼執行慢速分配,請求“stop the world”同步所有線程。

 

總結

“stop the world”相對於互斥變量來說是重量級的,但它只在不常發生的慢速分配中使用。同時,“stop the world”機制允許我們使用自旋鎖標誌作爲簡單的競爭檢測在多數快速分配的情況下使用。就總體架構上來說,這中方式比在多數情況下直接使用較慢的互斥變量有更高的效率(與自旋鎖檢測相較來說

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章