异步任务执行在纯 Java 代码中很常见,在响应式框架中更是如此。源文件中相邻的代码现在会在两个或更多不同的线程上执行。对于调试和分析来说,这些线程切换带来了两个问题:一方面,无法清楚地知道被调用操作的开销;另一方面,耗时操作无法追溯到导致其执行的代码。
JProfiler 针对调用是否停留在同一个 JVM,提供了不同的解决方案。如果异步执行发生在调用它的同一个 JVM 中,“内联异步执行” 调用树分析会计算一个包含调用点和执行站点的单一调用树。如果请求被发送到远程 JVM, 调用树会包含指向调用点和执行站点的超链接,因此你可以在显示相关 JVM 分析会话的不同 JProfiler 顶层窗口之间无缝跳转。
启用异步与远程请求跟踪
异步机制可以通过多种方式实现,无法以通用方式检测在单独线程或不同 JVM 上启动任务的语义。JProfiler 明确支持几种常见的异步与远程请求技术。你可以在请求跟踪设置中启用或禁用它们。 默认情况下,请求跟踪未启用。你也可以在会话启动对话框(会话启动前显示)中配置请求跟踪。
在 JProfiler 主窗口中,状态栏会指示是否启用了某些异步与远程请求跟踪类型,并为你提供配置对话框的快捷方式。
JProfiler 会检测被分析 JVM 中是否使用了未激活的异步请求跟踪类型,并在状态栏的异步与远程请求跟踪图标旁显示 通知图标。点击通知图标可以激活检测到的跟踪类型。异步与远程请求跟踪可能带来较大开销,建议仅在必要时启用。
Async Tracking
只要激活了至少一种 Async Tracking 类型,CPU、自耗时、分配和探针记录的调用树与热点视图会显示所有已激活跟踪类型的信息,并带有一个用于计算“内联异步执行”调用树分析的按钮。在该分析的结果视图中,所有异步执行的调用树通过 “async execution”节点与调用点连接。 默认情况下,异步执行的度量不会累加到调用树的祖先节点上。由于有时需要查看聚合值,分析顶部的复选框允许你在需要时启用该功能。
在另一个线程上卸载任务的最简单方式是启动新线程。使用 JProfiler,可以通过激活“线程启动”请求跟踪类型,从线程创建跟踪到执行站点。但线程是重量级对象,通常会被重复复用,因此该请求跟踪类型更适合调试用途。
在其他线程上启动任务最重要且通用的方式是使用 java.util.concurrent 包中的 executor。executor 也是许多处理异步执行的第三方库的基础。通过支持
executor,JProfiler 支持了处理多线程和并行编程的一整类库。
除了上述通用情况,JProfiler 还支持 JVM 的两个 GUI 工具包:AWT 和 SWT。两者都是单线程模型,即有一个特殊的事件分发线程用于操作 GUI 组件和绘制。为了避免阻塞
GUI,耗时任务需在后台线程中执行。但后台线程通常需要更新 GUI 以显示进度或完成情况,这通常通过特殊方法将 Runnable 安排到事件分发线程上执行来实现。
在 GUI 编程中,你经常需要跟踪多次线程切换以关联因果关系:用户在事件分发线程上发起操作,进而通过 executor 启动后台操作,后台操作完成后又将操作推送到事件分发线程。如果最后的操作出现性能问题,它距离最初事件已经发生了两次线程切换。
最后,JProfiler 支持 Kotlin 协程,这是 Kotlin 针对所有后端实现的多线程解决方案。异步执行本身就是协程启动的时刻。Kotlin 协程的调度机制非常灵活,甚至可以在当前线程上启动,这种情况下,“async execution”节点会有一个内联部分,并在节点文本中单独显示。
可挂起方法可能会中断执行,随后可能在不同线程上恢复。检测到挂起的相关方法会有一个额外的 “suspend”图标,鼠标悬停可显示实际调用次数与方法语义调用次数的提示。 Kotlin 协程可以被主动挂起,但由于它们不绑定线程,等待时间不会出现在调用树的任何位置。若要查看协程执行完成所用总时间,会在“async execution”节点下方添加 “suspended”时间节点,捕获整个协程的挂起时间。 根据你关注的是异步执行的 CPU 时间还是挂钟时间,可以通过分析顶部的“显示挂起时间”复选框随时添加或移除这些节点。
跟踪未被分析的调用点
默认情况下,executor 和 Kotlin 协程跟踪只跟踪调用点位于被分析类中的异步执行。这是因为框架和库可能以与你自身代码无关的方式使用这些异步机制,增加的调用点和执行站点只会带来额外开销和干扰。但在某些场景下需要跟踪未被分析的调用点。例如,框架可能启动一个 Kotlin 协程,随后你的代码在其中执行。
如果检测到未被分析类中的调用点,调用树和热点视图中的跟踪信息会显示相应的通知消息。在实时会话中,你可以分别为 executor 和 Kotlin 协程跟踪单独开启未被分析调用点的跟踪,这些选项可直接在视图中切换。 这些选项可随时在会话设置对话框的“CPU 分析”步骤中更改。
需要注意的是,只有在 CPU 录制处于活动状态时启动的 Kotlin 协程才能被跟踪。如果你稍后才开始 CPU 录制,Kotlin 协程的异步执行无法被内联。JProfiler 会像检测未被分析类调用点一样通知你。如果需要分析在应用启动时就已启动的长生命周期协程,则不能使用 attach mode。在这种情况下,请使用 -agentpath VM 参数启动 JVM,并在启动时立即开始 CPU 录制。
远程请求跟踪
对于部分通信协议,JProfiler 能够插入元数据并跨 JVM 边界跟踪请求。支持的技术包括:
-
HTTP: HttpURLConnection, java.net.http.HttpClient, Apache Http Client 4.x, Apache Async Http Client 4.x, OkHttp 3.9+ 客户端,任意 Servlet-API 实现或 Jetty(无 Servlet)服务端
HTTP:HttpURLConnection、java.net.http.HttpClient、Apache Http Client 4.x、Apache Async Http Client 4.x、OkHttp 3.9+(客户端),任意 Servlet-API 实现或 Jetty(无 Servlet)(服务端) -
Additional support for async JAX-RS calls for Jersey Async Client 2.x, RestEasy Async Client 3.x, Cxf Async Client 3.1.1+
额外支持 Jersey Async Client 2.x、RestEasy Async Client 3.x、Cxf Async Client 3.1.1+ 的异步 JAX-RS 调用 -
Web services: JAX-WS-RI, Apache Axis2 and Apache CXF
Web 服务:JAX-WS-RI、Apache Axis2 和 Apache CXF - RMI
- gRPC
-
Remote EJB calls: JBoss 7.1+ and Weblogic 11+
远程 EJB 调用:JBoss 7.1+ 和 Weblogic 11+
若要在 JProfiler 中跟踪请求,必须同时分析两个 VM,并在不同的 JProfiler 顶层窗口中同时打开。这适用于实时会话和快照。 如果目标 JVM 当前未打开,或远程调用时未激活 CPU 录制,点击调用点的超链接会显示错误消息。
跟踪远程请求时,JProfiler 会在相关 JVM 的调用树中明确显示调用点和执行站点。调用点是执行已记录远程请求前的最后一个被分析方法调用。它会在另一个 VM 的执行站点启动任务。 JProfiler 允许你通过调用树视图中的超链接,在调用点和执行站点之间跳转。
对于所有线程,调用点在远程请求跟踪中具有相同的标识。这意味着在调用点和执行站点之间跳转时,无需线程解析,跳转总是激活“所有线程组”以及“所有线程状态”线程状态选择,从而保证目标一定包含在显示的树中。
调用点和执行站点是一对多关系。一个调用点可以在多个执行站点上启动远程任务,尤其是它们位于不同远程 VM 时。在同一个 VM 中,单个调用点对应多个执行站点较少见,因为它们必须出现在不同的调用栈上。 如果一个调用点调用了多个执行站点,你可以在对话框中选择其中一个。
执行站点是调用树中的一个合成节点,包含由同一个调用点启动的所有执行。执行站点节点中的超链接可以跳回到该调用点。
如果同一个调用点多次调用同一个执行站点,执行站点会显示所有调用的合并调用树。如果不希望这样,可以使用 异常方法功能进一步拆分调用树,如下图所示。
与只被单个调用点引用的执行站点不同,调用点本身可以链接到多个执行站点。通过调用点的数字 ID,你可以在不同执行站点引用时识别同一个调用点。此外,调用点还会显示远程 VM 的 ID。被分析 VM 的 ID 可在状态栏中看到。它不是 JProfiler 内部管理的唯一 ID,而是一个显示 ID,从 1 开始,每打开一个新的被分析 VM 就递增。