您好,登錄后才能下訂單哦!
這篇文章主要講解了“AsyncGetCallTrace源碼底層原理是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“AsyncGetCallTrace源碼底層原理是什么”吧!
AsyncGetCallTrace 是由 OracleJDK/OpenJDK 內部提供的一個函數,該函數可以在 JVM 未進入 safepoint 時正常獲取到當前線程的調用棧(換句話說,使用該函數獲取線程棧時,不會要求 JVM 進入 safepoint。而進入 safepoint 對于 OpenJDK或者 OracleJDK 來說意味著會 STW 的發生,所以這意味著使用該函數獲取線程棧不會產生 STW,It’s amazing.)。目前該函數僅在 Linux X86、Solaris SPARC、Solaris X86 系統下支持。
另外它還支持在 UNIX 信號處理器中被異步調用,那么我們只需注冊一個 UNIX 信號處理器,并在Handler中調用 AsyncGetCallTrace 獲取當前線程的調用棧即可。由于 UNIX 信號會被隨機的發送給進程的某一線程進行處理,因此可以認為獲取所有線程的調用棧樣本是均勻的。
但是值得注意的是,該函數不是標準的 JVM API,所以使用的時候,可能存在以下問題:
移植性問題,因為只能跑在 OpenJDK 或者 OracleJDK 上
由于不是標準的 JVMTI API,所以使用者需要通過特殊一些方式來獲取該函數,這給使用者帶來了一些不便,但是這也無大礙。
關于怎么使用該函數去進行熱點方法采樣的方法,不在本節的討論范圍,在參考資料中,有一些描述,如果還有不清楚的,也可以給我留言交流。
// call frame copied from old .h file and renamed // Fields: // 1) For Java frame (interpreted and compiled), // lineno - bci of the method being executed or -1 if bci is not available // method_id - jmethodID of the method being executed // 2) For native method // lineno - (-3) // method_id - jmethodID of the method being executed typedef struct { jint lineno; // numberline number in the source file jmethodID method_id; // method executed in this frame } ASGCT_CallFrame; // call trace copied from old .h file and renamed // Fields: // env_id - ID of thread which executed this trace. // num_frames - number of frames in the trace. // (< 0 indicates the frame is not walkable). // frames - the ASGCT_CallFrames that make up this trace. Callee followed by callers. typedef struct { JNIEnv *env_id; // Env where trace was recorded jint num_frames; // number of frames in this trace ASGCT_CallFrame *frames; // frames } ASGCT_CallTrace; // These name match the names reported by the forte quality kit // 這些枚舉是對應到 ASGCT_CallTrace 中的 num_frames 的返回值的 // 舉個例子,當 JVM 在進行 GC 時,返回值中 // ASGCT_CallTrace.num_frames == ticks_GC_active enum { ticks_no_Java_frame = 0, ticks_no_class_load = -1, ticks_GC_active = -2, ticks_unknown_not_Java = -3, ticks_not_walkable_not_Java = -4, ticks_unknown_Java = -5, ticks_not_walkable_Java = -6, ticks_unknown_state = -7, ticks_thread_exit = -8, ticks_deopt = -9, ticks_safepoint = -10 };
// trace - trace data structure to be filled by the VM. // depth - depth of the call stack trace. // ucontext - ucontext_t of the LWP void AsyncGetCallTrace(ASGCT_CallTrace *trace, jint depth, void* ucontext)
該函數的調用者獲取到的棧是屬于某一個特定線程的,這個線程是由trace->env_id
唯一標識的,而且 該標識的線程 必須和當前執行 AsyncGetCallTrace 方法的 線程 是同一線程。同時調用者需要為 trace->frames 分配足夠多的內存,來保存棧深最多為 depth 的棧。若獲取到了有關的棧,JVM 會自動把相關的堆棧信息寫入 trace 中。接下來我們通過源碼來看看內部實現。
具體分析直接看代碼里面的注釋,為了保持完整性,該方法的任何代碼我都沒刪除,源碼位置:/hotspot/src/share/vm/prims/forte.cpp
void AsyncGetCallTrace(ASGCT_CallTrace *trace, jint depth, void* ucontext) { JavaThread* thread; // 1. 首先判斷 jniEnv 是否為空,或者 jniEnv 對應的線程是否有效,或者該線程是否已經退出, // 任一條件滿足,則返回 ticks_thread_exit(對應為核心數據結構中枚舉類型所示) if (trace->env_id == NULL || (thread = JavaThread::thread_from_jni_environment(trace->env_id)) == NULL || thread->is_exiting()) { // bad env_id, thread has exited or thread is exiting trace->num_frames = ticks_thread_exit; // -8 return; } if (thread->in_deopt_handler()) { // thread is in the deoptimization handler so return no frames trace->num_frames = ticks_deopt; // -9 return; } // 2. 這里對 jniEnv 所指線程是否是當前線程進行斷言,如果不相等則直接報錯 assert(JavaThread::current() == thread, "AsyncGetCallTrace must be called by the current interrupted thread"); // 3. JVMTI_EVENT_CLASS_LOAD 事件必須 enable,否則直接返回 ticks_no_class_load if (!JvmtiExport::should_post_class_load()) { trace->num_frames = ticks_no_class_load; // -1 return; } // 4. 當前 heap 必須沒有進行 GC ,否則直接返回 ticks_GC_active if (Universe::heap()->is_gc_active()) { trace->num_frames = ticks_GC_active; // -2 return; } // 5. 根據線程的當前狀態來獲取對應的線程棧,只有在線程的狀態為 _thread_in_vm/_thread_in_vm_trans // 和 _thread_in_Java/_thread_in_Java_trans 時才會進行線程棧的爬取 switch (thread->thread_state()) { case _thread_new: case _thread_uninitialized: case _thread_new_trans: // We found the thread on the threads list above, but it is too // young to be useful so return that there are no Java frames. trace->num_frames = 0; break; case _thread_in_native: case _thread_in_native_trans: case _thread_blocked: case _thread_blocked_trans: case _thread_in_vm: case _thread_in_vm_trans: { frame fr; // 首先獲取當前線程的棧頂棧幀 // param isInJava == false - indicate we aren't in Java code if (!thread->pd_get_top_frame_for_signal_handler(&fr, ucontext, false)) { trace->num_frames = ticks_unknown_not_Java; // -3 unknown frame } else { // 該線程如果沒有任何的 Java 棧幀,直接返回 0 幀 if (!thread->has_last_Java_frame()) { trace->num_frames = 0; // No Java frames } else { trace->num_frames = ticks_not_walkable_not_Java; // -4 non walkable frame by default // 如果存在合法的棧幀,則填充 trace 中的 frames 和 num_frames forte_fill_call_trace_given_top(thread, trace, depth, fr); ... } } } break; case _thread_in_Java: case _thread_in_Java_trans: { frame fr; // param isInJava == true - indicate we are in Java code if (!thread->pd_get_top_frame_for_signal_handler(&fr, ucontext, true)) { trace->num_frames = ticks_unknown_Java; // -5 unknown frame } else { trace->num_frames = ticks_not_walkable_Java; // -6, non walkable frame by default forte_fill_call_trace_given_top(thread, trace, depth, fr); } } break; default: // Unknown thread state trace->num_frames = ticks_unknown_state; // -7 break; } }
從以上的分析中,最終獲取線程棧的地方主要在這兩個方法 pd_get_top_frame_for_signal_handler
和 forte_fill_call_trace_given_top
,接下來我們來看下這兩個方法的實現。
從方法名不難看出,該方法的主要作用是獲取當前線程的棧頂幀。后面跟了個 signal_handler,最初的想法我猜應該是為響應 UNIX 下的 SIGPROF
信號的。因為本身 AsyncGetCallTrace
就是為此而生的。該方法的源碼位置 /hotspot/src/os_cpu/linux_x86/vm/thread_linux_x86.cpp
bool JavaThread::pd_get_top_frame_for_signal_handler(frame* fr_addr, void* ucontext, bool isInJava) { assert(Thread::current() == this, "caller must be current thread"); return pd_get_top_frame(fr_addr, ucontext, isInJava); }
很簡單,判斷一下是否是當前線程,至于 isInJava 入參是和當前的線程的狀態相關的,如果是跑在 java 代碼內,則為 true,否則為 false。
bool JavaThread::pd_get_top_frame(frame* fr_addr, void* ucontext, bool isInJava) { assert(this->is_Java_thread(), "must be JavaThread"); JavaThread* jt = (JavaThread *)this; // If we have a last_Java_frame, then we should use it even if // isInJava == true. It should be more reliable than ucontext info. if (jt->has_last_Java_frame() && jt->frame_anchor()->walkable()) { *fr_addr = jt->pd_last_frame(); return true; } // At this point, we don't have a last_Java_frame, so // we try to glean some information out of the ucontext // if we were running Java code when SIGPROF came in. if (isInJava) { ucontext_t* uc = (ucontext_t*) ucontext; intptr_t* ret_fp; intptr_t* ret_sp; ExtendedPC addr = os::Linux::fetch_frame_from_ucontext(this, uc, &ret_sp, &ret_fp); if (addr.pc() == NULL || ret_sp == NULL ) { // ucontext wasn't useful return false; } frame ret_frame(ret_sp, ret_fp, addr.pc()); if (!ret_frame.safe_for_sender(jt)) { #ifdef COMPILER2 // C2 uses ebp as a general register see if NULL fp helps frame ret_frame2(ret_sp, NULL, addr.pc()); if (!ret_frame2.safe_for_sender(jt)) { // nothing else to try if the frame isn't good return false; } ret_frame = ret_frame2; #else // nothing else to try if the frame isn't good return false; #endif /* COMPILER2 */ } *fr_addr = ret_frame; return true; } // nothing else to try return false; }
實際上拿棧頂幀的函數,由于函數的源碼較長,我就簡短的描述一下邏輯
當前線程只能是 java 線程
判斷棧頂幀是否存在,并且當前的棧是 walkable 的,若二者的滿足,則返回 javaThread 的 pd_last_frame,即棧頂幀,結束;否則繼續;
如果當前線程是跑 java 代碼,那么我們嘗試在 ucontext_t 內收集一些我們需要的信息,比如說棧幀
當我們獲取到棧頂的幀之后,接下來的事情就順理成章了,只要從棧頂開始,遍歷整個堆棧就能把所有的方法都獲取到了,同時將獲取到的結果保存到ASGCT_CallTrace
,源碼位置:/hotspot/src/share/vm/prims/forte.cpp
static void forte_fill_call_trace_given_top(JavaThread* thd, ASGCT_CallTrace* trace, int depth, frame top_frame) { NoHandleMark nhm; frame initial_Java_frame; Method* method; int bci = -1; // assume BCI is not available for method // update with correct information if available int count; count = 0; assert(trace->frames != NULL, "trace->frames must be non-NULL"); // 1. 獲取到棧頂的第一個 java 棧幀 // Walk the stack starting from 'top_frame' and search for an initial Java frame. find_initial_Java_frame(thd, &top_frame, &initial_Java_frame, &method, &bci); // Check if a Java Method has been found. if (method == NULL) return; // 2. 如果不是合法的方法,直接返回 if (!method->is_valid_method()) { trace->num_frames = ticks_GC_active; // -2 return; } vframeStreamForte st(thd, initial_Java_frame, false); // 循環迭代棧上的所有棧幀,一一獲取每個方法 bci 和 方法 id,這里會用上從外面傳入的最大棧深 depth for (; !st.at_end() && count < depth; st.forte_next(), count++) { bci = st.bci(); method = st.method(); if (!method->is_valid_method()) { // we throw away everything we've gathered in this sample since // none of it is safe trace->num_frames = ticks_GC_active; // -2 return; } // 根據方法對象獲取方法 id,如果方法 id 在此時還未產生,則返回 NULL trace->frames[count].method_id = method->find_jmethod_id_or_null(); // 如果方法是不是 native 方法,則把 lineno 設置為 bci 的值,否則置 -3 if (!method->is_native()) { trace->frames[count].lineno = bci; } else { trace->frames[count].lineno = -3; } } trace->num_frames = count; return; }
感謝各位的閱讀,以上就是“AsyncGetCallTrace源碼底層原理是什么”的內容了,經過本文的學習后,相信大家對AsyncGetCallTrace源碼底層原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。