欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入剖析理解AsyncGetCallTrace源碼底層原理

 更新時(shí)間:2022年02月09日 17:22:32   作者:西湖の風(fēng)  
這篇文章主要為大家介紹了AsyncGetCallTrace源碼的深層原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步

前言

AsyncGetCallTrace 是由 OracleJDK/OpenJDK 內(nèi)部提供的一個(gè)函數(shù),該函數(shù)可以在 JVM 未進(jìn)入 safepoint 時(shí)正常獲取到當(dāng)前線(xiàn)程的調(diào)用棧(換句話(huà)說(shuō),使用該函數(shù)獲取線(xiàn)程棧時(shí),不會(huì)要求 JVM 進(jìn)入 safepoint。而進(jìn)入 safepoint 對(duì)于 OpenJDK或者 OracleJDK 來(lái)說(shuō)意味著會(huì) STW 的發(fā)生,所以這意味著使用該函數(shù)獲取線(xiàn)程棧不會(huì)產(chǎn)生 STW,It’s amazing.)。目前該函數(shù)僅在 Linux X86、Solaris SPARC、Solaris X86 系統(tǒng)下支持。
另外它還支持在 UNIX 信號(hào)處理器中被異步調(diào)用,那么我們只需注冊(cè)一個(gè) UNIX 信號(hào)處理器,并在Handler中調(diào)用 AsyncGetCallTrace 獲取當(dāng)前線(xiàn)程的調(diào)用棧即可。由于 UNIX 信號(hào)會(huì)被隨機(jī)的發(fā)送給進(jìn)程的某一線(xiàn)程進(jìn)行處理,因此可以認(rèn)為獲取所有線(xiàn)程的調(diào)用棧樣本是均勻的。
但是值得注意的是,該函數(shù)不是標(biāo)準(zhǔn)的 JVM API,所以使用的時(shí)候,可能存在以下問(wèn)題:

移植性問(wèn)題,因?yàn)橹荒芘茉?OpenJDK 或者 OracleJDK 上

由于不是標(biāo)準(zhǔn)的 JVMTI API,所以使用者需要通過(guò)特殊一些方式來(lái)獲取該函數(shù),這給使用者帶來(lái)了一些不便,但是這也無(wú)大礙。

源碼實(shí)現(xiàn)

關(guān)于怎么使用該函數(shù)去進(jìn)行熱點(diǎn)方法采樣的方法,不在本節(jié)的討論范圍,在參考資料中,有一些描述,如果還有不清楚的,也可以給我留言交流。

核心數(shù)據(jù)結(jié)構(gòu)

// 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
// 這些枚舉是對(duì)應(yīng)到 ASGCT_CallTrace 中的 num_frames 的返回值的
// 舉個(gè)例子,當(dāng) JVM 在進(jìn)行 GC 時(shí),返回值中
// 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
};

函數(shù)申明

//   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)

該函數(shù)的調(diào)用者獲取到的棧是屬于某一個(gè)特定線(xiàn)程的,這個(gè)線(xiàn)程是由trace->env_id 唯一標(biāo)識(shí)的,而且 該標(biāo)識(shí)的線(xiàn)程 必須和當(dāng)前執(zhí)行 AsyncGetCallTrace 方法的 線(xiàn)程 是同一線(xiàn)程。同時(shí)調(diào)用者需要為 trace->frames 分配足夠多的內(nèi)存,來(lái)保存棧深最多為 depth 的棧。若獲取到了有關(guān)的棧,JVM 會(huì)自動(dòng)把相關(guān)的堆棧信息寫(xiě)入 trace 中。接下來(lái)我們通過(guò)源碼來(lái)看看內(nèi)部實(shí)現(xiàn)。

AsyncGetCallTrace 實(shí)現(xiàn)

具體分析直接看代碼里面的注釋?zhuān)瑸榱吮3滞暾?,該方法的任何代碼我都沒(méi)刪除,源碼位置:/hotspot/src/share/vm/prims/forte.cpp

void AsyncGetCallTrace(ASGCT_CallTrace *trace, jint depth, void* ucontext) {
  JavaThread* thread;
  // 1. 首先判斷 jniEnv 是否為空,或者 jniEnv 對(duì)應(yīng)的線(xiàn)程是否有效,或者該線(xiàn)程是否已經(jīng)退出,
  // 任一條件滿(mǎn)足,則返回 ticks_thread_exit(對(duì)應(yīng)為核心數(shù)據(jù)結(jié)構(gòu)中枚舉類(lèi)型所示)
  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. 這里對(duì) jniEnv 所指線(xiàn)程是否是當(dāng)前線(xiàn)程進(jìn)行斷言,如果不相等則直接報(bào)錯(cuò)
  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. 當(dāng)前 heap 必須沒(méi)有進(jìn)行 GC ,否則直接返回 ticks_GC_active
  if (Universe::heap()->is_gc_active()) {
    trace->num_frames = ticks_GC_active; // -2
    return;
  }
  // 5. 根據(jù)線(xiàn)程的當(dāng)前狀態(tài)來(lái)獲取對(duì)應(yīng)的線(xiàn)程棧,只有在線(xiàn)程的狀態(tài)為 _thread_in_vm/_thread_in_vm_trans 
  // 和 _thread_in_Java/_thread_in_Java_trans 時(shí)才會(huì)進(jìn)行線(xiàn)程棧的爬取
  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;
      // 首先獲取當(dāng)前線(xiàn)程的棧頂棧幀
      // 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 {
        //  該線(xiàn)程如果沒(méi)有任何的 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;
  }
}

從以上的分析中,最終獲取線(xiàn)程棧的地方主要在這兩個(gè)方法 pd_get_top_frame_for_signal_handler 和 forte_fill_call_trace_given_top,接下來(lái)我們來(lái)看下這兩個(gè)方法的實(shí)現(xiàn)。

pd_get_top_frame_for_signal_handler 實(shí)現(xiàn)

從方法名不難看出,該方法的主要作用是獲取當(dāng)前線(xiàn)程的棧頂幀。后面跟了個(gè) signal_handler,最初的想法我猜應(yīng)該是為響應(yīng) UNIX 下的 SIGPROF 信號(hào)的。因?yàn)楸旧?nbsp;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);
}

很簡(jiǎn)單,判斷一下是否是當(dāng)前線(xiàn)程,至于 isInJava 入?yún)⑹呛彤?dāng)前的線(xiàn)程的狀態(tài)相關(guān)的,如果是跑在 java 代碼內(nèi),則為 true,否則為 false。

pd_get_top_frame 實(shí)現(xiàn)

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;
}

實(shí)際上拿棧頂幀的函數(shù),由于函數(shù)的源碼較長(zhǎng),我就簡(jiǎn)短的描述一下邏輯

  • 當(dāng)前線(xiàn)程只能是 java 線(xiàn)程
  • 判斷棧頂幀是否存在,并且當(dāng)前的棧是 walkable 的,若二者的滿(mǎn)足,則返回 javaThread 的 pd_last_frame,即棧頂幀,結(jié)束;否則繼續(xù);
  • 如果當(dāng)前線(xiàn)程是跑 java 代碼,那么我們嘗試在 ucontext_t 內(nèi)收集一些我們需要的信息,比如說(shuō)棧幀

forte_fill_call_trace_given_top 實(shí)現(xiàn)

當(dāng)我們獲取到棧頂?shù)膸?,接下?lái)的事情就順理成章了,只要從棧頂開(kāi)始,遍歷整個(gè)堆棧就能把所有的方法都獲取到了,同時(shí)將獲取到的結(jié)果保存到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. 獲取到棧頂?shù)牡谝粋€(gè) 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);
  // 循環(huán)迭代棧上的所有棧幀,一一獲取每個(gè)方法 bci 和 方法 id,這里會(huì)用上從外面?zhèn)魅氲淖畲髼I?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;
    }
    // 根據(jù)方法對(duì)象獲取方法 id,如果方法 id 在此時(shí)還未產(chǎn)生,則返回 NULL
    trace->frames[count].method_id = method->find_jmethod_id_or_null();

    // 如果方法是不是 native 方法,則把 lineno 設(shè)置為 bci 的值,否則置 -3 
    if (!method->is_native()) {
      trace->frames[count].lineno = bci;
    } else {
      trace->frames[count].lineno = -3;
    }
  }
  trace->num_frames = count;
  return;
}

總結(jié)

源碼貼的有點(diǎn)多,這里稍微做一個(gè)小的總結(jié),同時(shí)也說(shuō)明一下使用時(shí)的一些注意事項(xiàng)

  • AsyncGetCallTrace 是 OpenJDK/OracleJDK 提供的可以在不暫停虛擬機(jī)的情況下可以獲取線(xiàn)程棧的函數(shù),開(kāi)發(fā)人員的主要觸發(fā)點(diǎn)是通過(guò) UNIX 的 SIGPROF 信號(hào)來(lái)觸發(fā)信號(hào)的 handler 來(lái)調(diào)用此函數(shù),來(lái)隨機(jī)獲取某一個(gè)線(xiàn)程的棧,為高性能的熱點(diǎn)方法監(jiān)控提供了可行的技術(shù)支持;
  • AsyncGetCallTrace 的使用必須在 Agent onload 的時(shí)候 Enable JVMTI_EVENT_CLASS_LOAD 和 JVMTI_EVENT_CLASS_PREPARE 事件,不然無(wú)法獲取相關(guān)的方法,同時(shí)還需要注冊(cè) callbacks.ClassPrepare 事件,在 class 加載準(zhǔn)備階段預(yù)先生成好 jMethodId,不然可能出現(xiàn) jMethodId 為空的情況;
  • 實(shí)際上,AsyncGetCallTrace 還可以認(rèn)為是標(biāo)準(zhǔn) JVM-TI 中的 GetCallTrace 接口的線(xiàn)程安全版本。但是我們看到實(shí)際上,這個(gè)方法中所有代碼都是未加鎖的,為啥?細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn),因?yàn)樵摵瘮?shù)的調(diào)用是用信號(hào)處理函數(shù)調(diào)用,且只有某一個(gè)單獨(dú)線(xiàn)程同時(shí)運(yùn)行它,所以它的使用場(chǎng)景就天然決定了它是線(xiàn)程安全的。
  • 還有最后一點(diǎn)要注意,由于該方法的調(diào)用是在 java 線(xiàn)程中調(diào)用的,所以當(dāng)使用者發(fā)送 SIGPROF 信號(hào)時(shí),恰好由于進(jìn)程處于 GC 階段,而導(dǎo)致 java 線(xiàn)程處于安全點(diǎn)而被阻塞,從而導(dǎo)致此時(shí)無(wú)法執(zhí)行該方法而獲取線(xiàn)程棧的場(chǎng)景,或者在執(zhí)行過(guò)程中線(xiàn)程被有關(guān)安全點(diǎn)掛起而導(dǎo)致獲取線(xiàn)程棧失敗這兩個(gè)場(chǎng)景發(fā)生。

本文中難免存在一些錯(cuò)誤和不足,還望大家不吝指出,同時(shí)如果對(duì)文中描述存在任何疑問(wèn),也歡迎大家提出來(lái)討論。

以上就是深入剖析理解AsyncGetCallTrace源碼的詳細(xì)內(nèi)容,更多關(guān)于AsyncGetCallTrace源碼剖析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 關(guān)于Spring中@Lazy注解的使用

    關(guān)于Spring中@Lazy注解的使用

    這篇文章主要介紹了關(guān)于Spring中@Lazy注解的使用,@Lazy注解用于標(biāo)識(shí)bean是否需要延遲加載,沒(méi)加注解之前主要容器啟動(dòng)就會(huì)實(shí)例化bean,本文提供了部分實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2023-08-08
  • Assert.assertEquals的使用方法及注意事項(xiàng)說(shuō)明

    Assert.assertEquals的使用方法及注意事項(xiàng)說(shuō)明

    這篇文章主要介紹了Assert.assertEquals的使用方法及注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • spring通過(guò)filter,Interceptor統(tǒng)一處理ResponseBody的返回值操作

    spring通過(guò)filter,Interceptor統(tǒng)一處理ResponseBody的返回值操作

    這篇文章主要介紹了spring通過(guò)filter,Interceptor統(tǒng)一處理ResponseBody的返回值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • 10分鐘帶你理解Java中的反射

    10分鐘帶你理解Java中的反射

    反射是java中一種強(qiáng)大的工具,能夠使我們很方便的創(chuàng)建靈活的代碼,這篇文章帶大家十分鐘快速理解Java中的反射,有需要的可以參考借鑒。
    2016-08-08
  • @Configuration保證@Bean單例語(yǔ)義方法介紹

    @Configuration保證@Bean單例語(yǔ)義方法介紹

    這篇文章主要介紹了SpringBoot中的@Configuration與@Bean注解,在進(jìn)行項(xiàng)目編寫(xiě)前,我們還需要知道一個(gè)東西,就是SpringBoot對(duì)我們的SpringMVC還做了哪些配置,包括如何擴(kuò)展,如何定制,只有把這些都搞清楚了,我們?cè)谥笫褂貌艜?huì)更加得心應(yīng)手
    2023-01-01
  • springboot使用小工具之Lombok、devtools、Spring Initailizr詳解

    springboot使用小工具之Lombok、devtools、Spring Initailizr詳解

    這篇文章主要介紹了springboot使用小工具之Lombok、devtools、Spring Initailizr詳解,Lombok可以代替手寫(xiě)get、set、構(gòu)造方法等,需要idea裝插件lombok,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • java基于swing實(shí)現(xiàn)的五子棋游戲代碼

    java基于swing實(shí)現(xiàn)的五子棋游戲代碼

    這篇文章主要介紹了java基于swing實(shí)現(xiàn)的五子棋游戲代碼,主要涉及圖形界面與數(shù)組的用法,有不錯(cuò)的參考借鑒價(jià)值,需要的朋友可以參考下
    2014-11-11
  • 用java在web環(huán)境下上傳和下載文件的技巧

    用java在web環(huán)境下上傳和下載文件的技巧

    這篇文章主要介紹了用java在web環(huán)境下上傳和下載文件的技巧的相關(guān)資料
    2016-01-01
  • SpringBoot如何使用Undertow做服務(wù)器

    SpringBoot如何使用Undertow做服務(wù)器

    這篇文章主要介紹了SpringBoot如何使用Undertow做服務(wù)器,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • java動(dòng)態(tài)添加外部jar包到classpath的實(shí)例詳解

    java動(dòng)態(tài)添加外部jar包到classpath的實(shí)例詳解

    這篇文章主要介紹了java動(dòng)態(tài)添加外部jar包到classpath的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下
    2017-09-09

最新評(píng)論