溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何用源碼分析在linux上的safe point

發(fā)布時間:2021-10-29 10:50:41 來源:億速云 閱讀:132 作者:柒染 欄目:編程語言

這篇文章將為大家詳細講解有關如何用源碼分析在linux上的safe point,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

safe point 顧明思意,就是安全點,當需要jvm做一些操作的時候,需要把當前正在運行的線程進入一個安全點的狀態(tài)(也可以說停止狀態(tài)),這樣才能做一些安全的操作,比如線程的dump,堆棧的信息。

在jvm里面通常vm_thread(我們一直在談論的做一些屬于vm 份內(nèi)事情的線程) 和cms_thread(內(nèi)存回收的線程)做的操作,是需要將其他的線程通過調(diào)用SafepointSynchronize::begin 和 SafepointSynchronize:end來實現(xiàn)讓其他的線程進入或者退出safe point 的狀態(tài)。

通常safepoint 的有三種狀態(tài)

_not_synchronized

說明沒有任何打斷現(xiàn)在所有線程運行的操作,也就是vm thread, cms thread 沒有接到操作的指令

_synchronizing

vm thread,cms thread 接到操作指令,正在等待所有線程進入safe point

_synchronized

所有線程進入safe point, vm thread, cms thread 可以開始指令操作

Java線程的狀態(tài)

通常在java 進程中的Java 的線程有幾個不同的狀態(tài),如何讓這些線程進入safepoint 的狀態(tài)中,jvm是采用不同的方式

a. 正在解釋執(zhí)行

由于java是解釋性語言,而線程在解釋java 字節(jié)碼的時候,需要dispatch table,記錄方法地址進行跳轉的,那么這樣讓線程進入停止狀態(tài)就比較容易了,只要替換掉dispatch table 就可以了,讓線程知道當前進入softpoint 狀態(tài)。

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。

具體實現(xiàn)可以查看源碼

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 是不需要等待線程執(zhí)行完的,只需要在從native code 返回的時候去判斷一下 _state 的狀態(tài)就可以了。

在方法體里就是前面博客也出現(xiàn)過的 SafepointSynchronize::do_call_back()

inline static bool do_call_back() {    return (_state != _not_synchronized);  }

判斷了_state 不是_not_synchronized狀態(tài)

為了能讓線程從native code 回到java 的時候為了能讀到/設置正確線程的狀態(tài),通常的解決方法使用memory barrier,java 使用OrderAccess::fence(); 在匯編里使用__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory"); 保證從內(nèi)存里讀到正確的值,但是這種方法嚴重影響系統(tǒng)的性能,于是java使用了每個線程都有獨立的內(nèi)存頁來設置狀態(tài)。通過使用使用參數(shù)-XX:+UseMembar 參數(shù)使用memory barrier,默認是不打開的,也就是使用獨立的內(nèi)存頁來設置狀態(tài)。

c. 運行編譯的代碼

1. Poling page 頁面

Poling page是在jvm初始化啟動的時候會初始化的一個單獨的內(nèi)存頁面,這個頁面是讓運行的編譯過的代碼的線程進入停止狀態(tài)的關鍵。

在linux里面使用了mmap初始化,源碼如下

address polling_page = (address) ::mmap(NULL, Linux::page_size(), PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

2. 編譯

java 的JIT 會直接編譯一些熱門的源碼到機器碼,直接執(zhí)行而不需要在解釋執(zhí)行從而提高效率,在編譯的代碼中,當函數(shù)或者方法塊返回的時候會去訪問一個內(nèi)存poling頁面。

x86架構下

void LIR_Assembler::return_op(LIR_Opr result) {    assert(result->is_illegal() || !result->is_single_cpu() || result->as_register() == rax, "word returns are in rax,");    if (!result->is_illegal() && result->is_float_kind() && !result->is_xmm_register()) {      assert(result->fpu() == 0, "result must already be on TOS");    }     // Pop the stack before the safepoint code    __ remove_frame(initial_frame_size_in_bytes());     bool result_is_oop = result->is_valid() ? result->is_oop() : false;     // Note: we do not need to round double result; float result has the right precision    // the poll sets the condition code, but no data registers    AddressLiteral polling_page(os::get_polling_page() + (SafepointPollOffset % os::vm_page_size()),                                relocInfo::poll_return_type);     // NOTE: the requires that the polling page be reachable else the reloc    // goes to the movq that loads the address and not the faulting instruction    // which breaks the signal handler code     __ test32(rax, polling_page);     __ ret(0);  }

在前面提到的SafepointSynchronize::begin 函數(shù)源碼中

if (UseCompilerSafepoints && DeferPollingPageLoopCount < 0) {    // Make polling safepoint aware    guarantee (PageArmed == 0, "invariant") ;    PageArmed = 1 ;    os::make_polling_page_unreadable();  }

這里提到了2個參數(shù) UseCompilerSafepoints 和 DeferPollingPageLoopCount ,在默認的情況下這2個參數(shù)是true和-1

函數(shù)體將會調(diào)用os:make_polling_page_unreadable();在linux os 下具體實現(xiàn)是調(diào)用了mprotect(bottom,size,prot) 使polling 內(nèi)存頁變成不可讀。

3. 信號

到當編譯好的程序嘗試在去訪問這個不可讀的polling頁面的時候,在系統(tǒng)級別會產(chǎn)生一個錯誤信號SIGSEGV, 可以參考筆者的一篇博客中曾經(jīng)講過java 的信號處理,可以知道信號SIGSEGV的處理函數(shù)在x86體系下見下源碼:

JVM_handle_linux_signal(int sig,                          siginfo_t* info,                          void* ucVoid,                          int abort_if_unrecognized){     ....     if (sig == SIGSEGV && os::is_poll_address((address)info->si_addr)) {          stub = SharedRuntime::get_poll_stub(pc);        }      ....  }

在linux x86,64 bit的體系中,poll stub 的地址 就是 SafepointSynchronize::handle_polling_page_exception 詳細程序可見shareRuntime_x86_64.cpp

回到safepoint.cpp中,SafepointSynchronize::handle_polling_page_exception通過取出線程的safepoint_stat,調(diào)用函數(shù)void ThreadSafepointState::handle_polling_page_exception,***通過調(diào)用SafepointSynchronize::block(thread()); 來block當前線程。

d. block 狀態(tài)

當線程進入block狀態(tài)的時候,繼續(xù)保持block狀態(tài)。

關于如何用源碼分析在linux上的safe point就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI