async-profiler 基本原理

0. 是什么

This project is a low overhead sampling profiler for Java that does not suffer from Safepoint bias problem. It features HotSpot-specific APIs to collect stack traces and to track memory allocations. The profiler works with OpenJDK, Oracle JDK and other Java runtimes based on HotSpot JVM.

async-profiler can trace the following kinds of events:

  • CPU cycles
  • Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
  • Allocations in Java Heap
  • Contented lock attempts, including both Java object monitors and ReentrantLocks

一个没有 Safepoint bias 问题的 profiler,可以追踪 CPU 周期,也可以追踪 Java 堆中的分配、锁争用,以及软件和硬件的性能计数器。

1. 怎么用

async-profiler 是基于 JVMTI(JVM tool interface) 开发的 Agent,自然而然有两种使用方式:a.跟随 Java 进程启动,自动载入共享库;b.程序运行时通过 attach api 动态载入。

a. 启动时

$ java -agentpath:/path/to/libasyncProfiler.so=start,svg,file=profile.svg ...

b. 运行时

$ jps
9234 Jps
8983 Computey
$ ./profiler.sh start 8983
$ ./profiler.sh stop 8983

profiler.sh 脚本中封装了调用 attach api 的逻辑 —— jattach。

参数配置参考文档 README/Profiler Options

2. 实现

attach

通过本地 socket 与 JVM 进行交互的逻辑封装在 src/jattach/jattach.c,由于不是 profile 的重点逻辑,所以简单略过。

async-profiler 基本原理_第1张图片

JVMTI

JVMTI 入口函数实现的逻辑在 src/vmEntry.cpp

async-profiler 基本原理_第2张图片

通过 profiler.sh 脚本 attach 到 JVM 时就会调用 Agent_OnAttach 方法,如果是跟随 Java 进程启动那么会调用 Agent_OnLoad 方法。

首先进行一些初始化的逻辑,封装在 VM::init 方法中:

async-profiler 基本原理_第3张图片

L36 获取 JVM 环境。每个 agent 都有自己的环境,在指定 agent 行为前,首先要获取环境信息,后面的操作都是针对这个环境。

L47 添加 capabilities。JVMTI 中有很多事件,每个事件都对应一些 Capabilities,如果你想为此事件编写回调函数,首先得开启相应的 Capabilities。

Field Description Since
can_generate_all_class_hook_events Can generate ClassFileLoadHook events for every loaded class. 1.0
can_get_bytecodes Can get bytecodes of a method GetBytecodes 1.0
can_get_constant_pool Can get the constant pool of a class - GetConstantPool 1.0
can_get_source_file_name Can get the source file name of a class 1.0
can_get_line_numbers Can get the line number table of a method 1.0
can_generate_compiled_method_load_events Can generate events when a method is compiled or unloaded 1.0
can_generate_monitor_events Can generate events on monitor activity 1.0
can_tag_objects Can set and get tags, as described in the Heap category. 1.0

L61 注册回调函数。事件回调函数的接口是由JVMTI指定的。

Callback Description
VMInit The VM initialization event signals the completion of VM initialization. Once this event is generated, the agent is free to call any JNI or JVM TI function.
VMDeath The VM death event notifies the agent of the termination of the VM. No events will occur after the VMDeath event.
ClassLoad A class load event is generated when a class is first loaded.
ClassPrepare A class prepare event is generated when class preparation is complete.
CompiledMethodLoad Sent when a method is compiled and loaded into memory by the VM.
CompiledMethodUnload Sent when a compiled method is unloaded from memory.
DynamicCodeGenerated Sent when a component of the virtual machine is generated dynamically.
ThreadStart Thread start events are generated by a new thread before its initial method executes.
ThreadEnd Thread end events are generated by a terminating thread after its initial method has finished execution.
MonitorContendedEnter Sent when a thread is attempting to enter a Java programming language monitor already acquired by another thread.
MonitorContendedEntered Sent when a thread enters a Java programming language monitor after waiting for it to be released by another thread.

注:通过 attach 动态加载 agent 不会触发 VMInit 事件回调,因为此时 VM 已经初始化完毕;但是通过 -agentpath 跟随 Java 进程启动加载,在 Agent_OnLoad 入口方法执行后就会触发 VMInit 事件回调。

L63-L69 指定事件。当 JVM 发生一个事件,回调为此事件编写的函数,所以需要指定哪个事件发生时通知 agent。

注:ThreadStartThreadEndMonitorContendedEnterMonitorContendedEntered 事件的指定逻辑在 ProfilerLockTracer 启动和停止的方法中。

L71-L81 获取 AsyncGetCallTrace

如果是通过 attach 动态加载,那么加载所有的方法,并且生成 JVMTI_EVENT_DYNAMIC_CODE_GENERATED、JVMTI_EVENT_COMPILED_METHOD_LOAD 事件。

VM::init 初始化之后就是解析参数,最终调用 Profiler::run 方法执行 profile 的逻辑。

profiler

profiler 的逻辑位于 src/profiler.cpp

async-profiler 基本原理_第4张图片

async-profiler 基本原理_第5张图片

根据 action 执行对应的函数。

开始 profile 就会执行 start 方法:

async-profiler 基本原理_第6张图片

L530 分配 frame_buffer,如果需要存储的 call traces 大于这个缓存将会溢出。

L545-L551 初始化 Hotspot 和 JVMTI 相关的资源。

L554 如果要输出 JFR 那么启动 _jfr,相关格式输出的逻辑封装在 FlightRecorderRecording 中。

L560 根据 event 选择 engine,如果是支持 perf_events 的 linux 系统,那么还提供了很多系统底层的事件。

Basic events:
  cpu
  alloc
  lock
  wall
  itimer
Perf events:
  page-faults
  context-switches
  cycles
  instructions
  cache-references
  cache-misses
  branches
  branch-misses
  bus-cycles
  L1-dcache-load-misses
  LLC-load-misses
  dTLB-load-misses
  mem:breakpoint
  trace:tracepoint
async-profiler 基本原理_第7张图片

先看一下 EVENT_CPU 在 perf_events 的实现 PerfEvents

async-profiler 基本原理_第8张图片

L462 接收操作系统信号 SIGPROF 执行 signalHandler

L471 每个线程打开相应的 perf_events 计数器,注册 SIGPROF 事件。

async-profiler 基本原理_第9张图片

L425 回调 Profiler 采样调用栈信息

async-profiler 基本原理_第10张图片

L402 通过 AsyncGetCallTrace 异步获取调用栈。

L418 将调用栈存储到 _frame_buffer

async-profiler 基本原理_第11张图片

JDK-8178287 列举了一些无法获取调用栈的情况,这里做了一个补救方案,但是最终还得依赖 AsyncGetCallTrace 本身的修复更新。

其它类型 engine 的处理流程类似,具体的数据细节这里暂且略过。

回过头来在再看一下 ACTION_DUMP:

async-profiler 基本原理_第12张图片

首先关闭各种资源,_jfr 会在关闭的时候输出。

然后是根据配置各种 dump:

  • dumpCollapsed 输出 FlameGraph 脚本所需的格式
  • dumpFlameGraph 输出火焰图 svg 或者调用树 html
  • dumpSummary 输出汇总信息
  • dumpTraces 输出调用栈列表
  • dumpFlat 输出 top N hot methods

你可能感兴趣的:(async-profiler 基本原理)