最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言

AsyncGetCallTrace 是由 OracleJDK/OpenJDK 內部提供的一個函數(shù),該函數(shù)可以在 JVM 未進入 safepoint 時正常獲取到當前線程的調用棧(換句話說,使用該函數(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 信號處理器中被異步調用,那么我們只需注冊一個 UNIX 信號處理器,并在Handler中調用 AsyncGetCallTrace 獲取當前線程的調用棧即可。由于 UNIX 信號會被隨機的發(fā)送給進程的某一線程進行處理,因此可以認為獲取所有線程的調用棧樣本是均勻的。
但是值得注意的是,該函數(shù)不是標準的 JVM API,所以使用的時候,可能存在以下問題:

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

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

源碼實現(xiàn)

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

核心數(shù)據(jù)結構

// 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ù)的調用者獲取到的棧是屬于某一個特定線程的,這個線程是由trace->env_id 唯一標識的,而且 該標識的線程 必須和當前執(zhí)行 AsyncGetCallTrace 方法的 線程 是同一線程。同時調用者需要為 trace->frames 分配足夠多的內存,來保存棧深最多為 depth 的棧。若獲取到了有關的棧,JVM 會自動把相關的堆棧信息寫入 trace 中。接下來我們通過源碼來看看內部實現(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ù)結構中枚舉類型所示)
  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)相關的,如果是跑在 java 代碼內,則為 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,即棧頂幀,結束;否則繼續(xù);
  • 如果當前線程是跑 java 代碼,那么我們嘗試在 ucontext_t 內收集一些我們需要的信息,比如說棧幀

forte_fill_call_trace_given_top 實現(xiàn)

當我們獲取到棧頂?shù)膸螅酉聛淼氖虑榫晚樌沓烧铝?,只要從棧頂開始,遍歷整個堆棧就能把所有的方法都獲取到了,同時將獲取到的結果保存到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 設置為 bci 的值,否則置 -3 
    if (!method->is_native()) {
      trace->frames[count].lineno = bci;
    } else {
      trace->frames[count].lineno = -3;
    }
  }
  trace->num_frames = count;
  return;
}

總結

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

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

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

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

相關文章

  • 手把手帶你用java搞定青蛙跳臺階

    手把手帶你用java搞定青蛙跳臺階

    這篇文章主要給大家介紹了關于Java青蛙跳臺階問題的解決思路與代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-08-08
  • Java中處理XML數(shù)據(jù)的方法

    Java中處理XML數(shù)據(jù)的方法

    本文介紹了在Java中處理XML數(shù)據(jù)的幾種常見方法:DOM、SAX和JAXB,每種方法都有其適用的場景和優(yōu)缺點,具體選擇取決于項目的需求和性能考慮,這篇文章主要介紹了Java中處理XML數(shù)據(jù)的方法,需要的朋友可以參考下
    2024-06-06
  • Spring的refresh()方法相關異常解析

    Spring的refresh()方法相關異常解析

    這篇文章主要介紹了Spring的refresh()方法相關異常解析,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • java自定義切面增強方式(關于自定義注解aop)

    java自定義切面增強方式(關于自定義注解aop)

    這篇文章主要介紹了java自定義切面增強方式(關于自定義注解aop),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • JAVA中調用C語言函數(shù)的實現(xiàn)方式

    JAVA中調用C語言函數(shù)的實現(xiàn)方式

    這篇文章主要介紹了JAVA中調用C語言函數(shù)的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Java中Properties配置類用法詳解

    Java中Properties配置類用法詳解

    所謂的配置文件問題,是指我們在開發(fā)時,經(jīng)常需要讀取和修改一些配置信息,比如數(shù)據(jù)庫、消息隊列、Nginx、Web服務器等的配置,為了便于修改這些信息,我們可以采用Properties配置類,本文給大家講一下Properties配置類是怎么回事,以及怎么使用
    2023-06-06
  • java實現(xiàn)幸運抽獎系統(tǒng)

    java實現(xiàn)幸運抽獎系統(tǒng)

    這篇文章主要為大家詳細介紹了java實現(xiàn)幸運抽獎系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • Java中的對象和對象引用實例淺析

    Java中的對象和對象引用實例淺析

    這篇文章主要介紹了Java中的對象和對象引用,實例分析了對象與對象引用的概念與相關使用技巧,需要的朋友可以參考下
    2015-05-05
  • Java進程cpu頻繁100%問題解決方案

    Java進程cpu頻繁100%問題解決方案

    這篇文章主要介紹了Java進程cpu頻繁100%問題解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-10-10
  • springboot整合netty框架的方式小結

    springboot整合netty框架的方式小結

    Netty 是一個基于NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發(fā)出一個網(wǎng)絡應用,這篇文章主要介紹了springboot整合netty框架的方式小結,需要的朋友可以參考下
    2022-06-06

最新評論

棋牌| 滨州市| 来凤县| 定南县| 安乡县| 报价| 威海市| 宜良县| 舟山市| 龙里县| 正安县| 辽源市| 莲花县| 深水埗区| 大同市| 东丽区| 东乡县| 黎城县| 绩溪县| 民权县| 北海市| 安义县| 青阳县| 洪泽县| 松潘县| 电白县| 隆化县| 青海省| 张掖市| 富宁县| 桂平市| 平安县| 莱阳市| 贺州市| 宁化县| 靖远县| 新丰县| 泰来县| 云林县| 莆田市| 仙桃市|