亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言

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

移植性問題,因為只能跑在 OpenJDK 或者 OracleJDK 上

由于不是標準的 JVMTI API,所以使用者需要通過特殊一些方式來獲取該函數(shù),這給使用者帶來了一些不便,但是這也無大礙。

源碼實現(xiàn)

關(guān)于怎么使用該函數(shù)去進行熱點方法采樣的方法,不在本節(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
// 這些枚舉是對應到 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
};

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

AsyncGetCallTrace 實現(xiàn)

具體分析直接看代碼里面的注釋,為了保持完整性,該方法的任何代碼我都沒刪除,源碼位置:/hotspot/src/share/vm/prims/forte.cpp

void AsyncGetCallTrace(ASGCT_CallTrace *trace, jint depth, void* ucontext) {
  JavaThread* thread;
  // 1. 首先判斷 jniEnv 是否為空,或者 jniEnv 對應的線程是否有效,或者該線程是否已經(jīng)退出,
  // 任一條件滿足,則返回 ticks_thread_exit(對應為核心數(shù)據(jù)結(jié)構(gòu)中枚舉類型所示)
  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. 根據(jù)線程的當前狀態(tài)來獲取對應的線程棧,只有在線程的狀態(tài)為 _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,接下來我們來看下這兩個方法的實現(xiàn)。

pd_get_top_frame_for_signal_handler 實現(xiàn)

從方法名不難看出,該方法的主要作用是獲取當前線程的棧頂幀。后面跟了個 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 入?yún)⑹呛彤斍暗木€程的狀態(tài)相關(guān)的,如果是跑在 java 代碼內(nèi),則為 true,否則為 false。

pd_get_top_frame 實現(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ù)的源碼較長,我就簡短的描述一下邏輯

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

forte_fill_call_trace_given_top 實現(xiàn)

當我們獲取到棧頂?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ù)牡谝粋€ 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)迭代棧上的所有棧幀,一一獲取每個方法 bci 和 方法 id,這里會用上從外面?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ù)方法對象獲取方法 id,如果方法 id 在此時還未產(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é)

源碼貼的有點多,這里稍微做一個小的總結(jié),同時也說明一下使用時的一些注意事項

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

本文中難免存在一些錯誤和不足,還望大家不吝指出,同時如果對文中描述存在任何疑問,也歡迎大家提出來討論。

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

相關(guān)文章

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

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

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

    Assert.assertEquals的使用方法及注意事項說明

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

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

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

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

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

    @Configuration保證@Bean單例語義方法介紹

    這篇文章主要介紹了SpringBoot中的@Configuration與@Bean注解,在進行項目編寫前,我們還需要知道一個東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴展,如何定制,只有把這些都搞清楚了,我們在之后使用才會更加得心應手
    2023-01-01
  • springboot使用小工具之Lombok、devtools、Spring Initailizr詳解

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

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

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

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

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

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

    SpringBoot如何使用Undertow做服務器

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

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

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

最新評論