我的编程空间,编程开发者的网络收藏夹
学习永远不晚

AsyncGetCallTrace源码底层原理是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

AsyncGetCallTrace源码底层原理是什么

这篇文章主要讲解了“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 executedtypedef 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_activeenum {  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 LWPvoid AsyncGetCallTrace(ASGCT_CallTrace *trace, jint depth, void* ucontext)

    该函数的调用者获取到的栈是属于某一个特定线程的,这个线程是由trace->env_id 唯一标识的,而且 该标识的线程 必须和当前执行 AsyncGetCallTrace 方法的 线程 是同一线程。同时调用者需要为 trace->frames 分配足够多的内存,来保存栈深最多为 depth 的栈。若获取到了有关的栈,JVM 会自动把相关的堆栈信息写入 trace 中。接下来我们通过源码来看看内部实现。

    AsyncGetCallTrace 实现

    具体分析直接看代码里面的注释,为了保持完整性,该方法的任何代码我都没删除,源码位置:/hotspot/class="lazy" data-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,接下来我们来看下这两个方法的实现。

    pd_get_top_frame_for_signal_handler 实现

    从方法名不难看出,该方法的主要作用是获取当前线程的栈顶帧。后面跟了个 signal_handler,最初的想法我猜应该是为响应 UNIX 下的 SIGPROF 信号的。因为本身 AsyncGetCallTrace 就是为此而生的。该方法的源码位置 /hotspot/class="lazy" data-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。

    pd_get_top_frame 实现

    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     }    *fr_addr = ret_frame;    return true;  }  // nothing else to try  return false;}

    实际上拿栈顶帧的函数,由于函数的源码较长,我就简短的描述一下逻辑

    • 当前线程只能是 java 线程

    • 判断栈顶帧是否存在,并且当前的栈是 walkable 的,若二者的满足,则返回 javaThread 的 pd_last_frame,即栈顶帧,结束;否则继续;

    • 如果当前线程是跑 java 代码,那么我们尝试在 ucontext_t 内收集一些我们需要的信息,比如说栈帧

    forte_fill_call_trace_given_top 实现

    当我们获取到栈顶的帧之后,接下来的事情就顺理成章了,只要从栈顶开始,遍历整个堆栈就能把所有的方法都获取到了,同时将获取到的结果保存到ASGCT_CallTrace,源码位置:/hotspot/class="lazy" data-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源码底层原理是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    AsyncGetCallTrace源码底层原理是什么

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    AsyncGetCallTrace源码底层原理是什么

    这篇文章主要讲解了“AsyncGetCallTrace源码底层原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“AsyncGetCallTrace源码底层原理是什么”吧!前言Async
    2023-06-29

    InnoDB底层原理是什么

    这篇文章主要为大家展示了“InnoDB底层原理是什么”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“InnoDB底层原理是什么”这篇文章吧。InnoDB,是MySQL的数据库引擎之一,现为MySQ
    2023-06-27

    Spring底层原理是什么

    这篇文章主要讲解了“Spring底层原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring底层原理是什么”吧!Spring简介ClassPathXmlApplicationCo
    2023-07-05

    HashMap的底层原理是什么

    这篇文章将为大家详细讲解有关HashMap的底层原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一:HashMap的节点:HashMap是一个集合,键值对的集合,源码中每个节点用No
    2023-06-04

    redis锁底层原理是什么

    Redis的锁底层原理是基于Redis的单线程特性和原子操作来实现的。当一个客户端尝试获取锁时,它会向Redis发送一个SETNX命令,该命令会在键不存在的情况下将键的值设置为指定的值。因为SETNX是一个原子操作,所以只有一个客户端能够成
    2023-09-06

    Python matplotlib底层原理是什么

    本篇内容介绍了“Python matplotlib底层原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. matplotlib 框
    2023-06-21

    Vue的底层原理是什么

    这篇文章主要介绍Vue的底层原理是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Observer (数据劫持)核心是通过Obeject.defineProperty()来监听数据的变动,这个函数内部可以定义set
    2023-06-29

    Java NIO底层原理是什么

    这篇文章主要介绍“Java NIO底层原理是什么”,在日常操作中,相信很多人在Java NIO底层原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java NIO底层原理是什么”的疑惑有所帮助!接下来
    2023-06-16

    golang channel底层原理是什么

    Golang的channel底层原理是基于通信顺序进程(Communicating Sequential Processes,简称CSP)模型实现的。在Golang中,channel是一种用于在goroutine之间进行通信和同步的机制。
    golang channel底层原理是什么
    2024-02-29

    Golang WaitGroup 底层原理及源码解析

    WaitGroup 是 Golang 中最常见的并发控制技术之一,它的作用我们可以简单类比为其他语言中多线程并发控制中的 join(),这篇文章主要介绍了Golang WaitGroup 底层原理及源码详解,需要的朋友可以参考下
    2023-05-18

    mongodb底层存储原理是什么

    MongoDB的底层存储原理是使用一种称为B树(B-Tree)的数据结构来存储数据。B树是一种平衡的多路搜索树,它通过将数据按照顺序存储在磁盘上的块中,以提高数据的访问效率。具体来说,MongoDB使用了一种称为Mmapv1的存储引擎,该存
    2023-09-06

    Spring Boot的底层原理是什么

    这篇文章主要讲解了“Spring Boot的底层原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring Boot的底层原理是什么”吧!1.基于你对springboot的理解描述
    2023-06-27

    PHP底层工作原理是什么

    这篇文章将为大家详细讲解有关PHP底层工作原理是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。PHP底层工作原理图1 php结构从图上可以看出,php从下到上是一个4层体系①Zend引擎Zend整体用
    2023-06-17

    源码分析Java中ThreadPoolExecutor的底层原理

    这篇文章主要带大家从源码分析一下Java中ThreadPoolExecutor的底层原理,文中的示例代码讲解详细,具有一定的学习价值,需要的可以参考一下
    2023-05-19

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录