safe point 顧明思意,就是安全點,當需要jvm做一些操作的時候,需要把當前正在啟動並執行線程進入一個安全點的狀態(也可以說停止狀態),這樣才能做一些安全的操作,比如線程的dump,堆棧的資訊。
在jvm裡面通常vm_thread(我們一直在談論的做一些屬於vm 份內事情的線程) 和cms_thread(記憶體回收的線程)做的操作,是需要將其他的線程通過調用SafepointSynchronize::begin 和 SafepointSynchronize:end來實現讓其他的線程進入或者退出safe point 的狀態。 通常safepoint 的有三種狀態
_not_synchronized |
說明沒有任何打斷現在所有線程啟動並執行操作,也就是vm thread, cms thread 沒有接到操作的指令 |
_synchronizing |
vm thread,cms thread 接到操作指令,正在等待所有線程進入safe point |
_synchronized |
所有線程進入safe point, vm thread, cms thread 可以開始指令操作 |
Java線程的狀態
通常在java 進程中的Java 的線程有幾個不同的狀態,如何讓這些線程進入safepoint 的狀態中,jvm是採用不同的方式 a. 正在解釋執行
由於java是解釋性語言,而線程在解釋java 位元組碼的時候,需要dispatch table,記錄方法地址進行跳轉的,那麼這樣讓線程進入停止狀態就比較容易了,只要替換掉dispatch table 就可以了,讓線程知道當前進入softpoint 狀態。
java裡會設定3個DispatchTable, _active_table, _normal_table, _safept_table
_active_table 正在解釋啟動並執行線程使用的dispatch table
_normal_table 就是正常啟動並執行初始化的dispatch table
_safept_table safe point需要的dispatch table
解釋啟動並執行線程一直都在使用_active_table,關鍵處就是在進入saftpoint 的時候,用_safept_table替換_active_table, 在退出saftpoint 的時候,使用_normal_table來替換_active_table
具體實現可以查看源碼
void TemplateInterpreter::notice_safepoints() { if (!_notice_safepoints) { // switch to safepoint dispatch table _notice_safepoints = true; copy_table((address*)&_safept_table, (address*)&_active_table, sizeof(_active_table) / sizeof(address)); }}// switch from the dispatch table which notices safepoints back to the// normal dispatch table. So that we can notice single stepping points,// keep the safepoint dispatch table if we are single stepping in JVMTI.// Note that the should_post_single_step test is exactly as fast as the// JvmtiExport::_enabled test and covers both cases.void TemplateInterpreter::ignore_safepoints() { if (_notice_safepoints) { if (!JvmtiExport::should_post_single_step()) { // switch to normal dispatch table _notice_safepoints = false; copy_table((address*)&_normal_table, (address*)&_active_table, sizeof(_active_table) / sizeof(address)); } }}
b. 運行在native code
如果線程運行在native code的時候,vm thread 是不需要等待線程執行完的,只需要在從native code 返回的時候去判斷一下 _state 的狀態就可以了。
在方法體裡就是前面部落格也出現過的 SafepointSynchronize::do_call_back()
inline static bool do_call_back() { return (_state != _not_synchronized); }
判斷了_state 不是_not_synchronized狀態
為了能讓線程從native code 回到java 的時候為了能讀到/設定正確線程的狀態,通常的解決方案使用memory barrier,java 使用OrderAccess::fence(); 在彙編裡使用__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory"); 保證從記憶體裡讀到正確的值,但是這種方法嚴重影響系統的效能,於是java使用了每個線程都有獨立的記憶體頁來設定狀態。通過使用使用參數-XX:+UseMembar 參數使用memory barrier,預設是不開啟的,也就是使用獨立的記憶體頁來設定狀態。