JProfiler帮助文档

线程分析 (Thread profiling)

不正确地使用线程可能会引发许多不同类型的问题。过多活跃线程可能导致线程饥饿 (thread starvation),线程之间可能会相互阻塞并影响应用程序的活性 (liveness),或者以错误的顺序获取锁可能会导致死锁 (deadlock)。此外,有关线程的信息对于调试也是至关重要的。

在 JProfiler 中,线程分析分为两个视图 (view) 区域:“Threads”部分负责处理线程的生命周期以及抓取线程转储 (thread dump);“Monitors & locks”部分则提供了用于分析多个线程之间交互的功能。

检查线程

线程历史视图 (thread history view) 在时间线中将每个线程显示为彩色行,其中的颜色指示所记录的线程状态。线程可以按其创建时间、名称或线程组进行排序,也可以按名称过滤。您还可以通过拖放自行重新排列线程的顺序。当记录了 monitor 事件时,您可以将鼠标悬停在处于“Waiting”或“Blocked”状态的线程部分上,查看关联的调用栈 (call stack),并通过链接跳转到 monitor 历史视图 (monitor history view)。

线程 monitor 视图 (thread monitor view) 中提供了所有线程的表格视图。如果在创建线程时 CPU 记录处于活动状态,JProfiler 将保存创建该线程的线程名称并在表格中显示。在底部会显示创建线程的调用栈。出于性能考虑,并不会向 JVM 请求实际的完整调用栈,而是使用 CPU 记录中的当前信息。这意味着调用栈中仅显示满足调用树 (call tree) 收集过滤器设置的类。

如果您在配置文件设置 (profiling setting) 中启用了预估 CPU 时间的记录,表格中将增加一个 CPU time 列。CPU 时间仅在您记录 CPU 数据时才会被测量。

与大多数调试器一样,JProfiler 也可以获取线程转储。线程转储的调用栈是由 JVM 提供的完整调用栈,不依赖于 CPU 记录。选中两个线程转储并单击 Show Difference 按钮,即可在 diff 查看器中对它们进行比较。还可以通过选中单个线程转储中的两个线程,并从上下文菜单中选择 Show Difference 来进行比较。

具有相同调用栈的线程转储会被分组。对于平台线程转储中的此类节点,提供了用于显示所有相似线程的上下文菜单操作。

当被分析 (profiled) 的是 Java 21 或更高版本时,线程转储视图中提供了单独的“Take Thread Dump With Virtual Threads”操作。虚拟线程转储中也会包含平台线程。

也可以通过“Trigger thread dump”触发器 (trigger) 操作或通过 API 获取线程转储。

分析锁定情况

每个 Java 对象都有一个关联的 monitor,可用于两种同步操作:线程可以在 monitor 上等待 (wait),直到另一个线程对其发出通知 (notify);或者线程可以获取 monitor 上的锁,此过程可能会阻塞,直到另一个线程释放锁的所有权。此外,Java 在 java.util.concurrent.locks 包中提供了类以实现更高级的锁定策略。该包中的锁不使用对象的 monitor,而是使用不同的原生实现。

JProfiler 可以记录上述两种机制的锁定情况。在锁定情况中,存在一个或多个线程、一个 monitor 或一个 java.util.concurrent.locks.Lock 实例,以及一个耗费一定时间的等待或阻塞操作。这些锁定情况在 monitor 历史视图中以表格形式呈现,并在锁定历史图形 (locking history graph) 中以可视化方式呈现。

锁定历史图形侧重于所有涉及的 monitor 与线程之间的整体关系集,而不是孤立的 monitor 事件持续时间。参与锁定情况的线程和 monitor 分别绘制为蓝色和灰色矩形,如果它们是死锁的一部分,则绘制为红色。黑色箭头表示 monitor 的所有权,黄色箭头从等待线程延伸到关联的 monitor,而红色虚线箭头表示线程想要获取 monitor 且当前处于阻塞状态。如果记录了 CPU 数据,将鼠标悬停在阻塞或等待箭头上时即可查看调用栈。这些提示框中包含超链接,可转到 monitor 历史视图中的对应行。

表格形式的 monitor 历史视图显示 monitor 事件。它们具有一个显示为列的持续时间,因此您可以通过对表格排序来找到最重要的事件。对于表格视图中的任何选定行,您可以使用 Show In Graph 操作跳转到图形 (graph)。

每个 monitor 事件都有一个关联的 monitor。Monitor Class 列显示使用其 monitor 的实例的类名,如果 monitor 未关联 Java 对象,则显示“[raw monitor]”。在任何情况下,monitor 都具有一个显示在单独列中的唯一 ID,因此您可以关联同一个 monitor 在多个事件中的使用情况。每个 monitor 事件都有一个执行操作的等待线程,以及可选的阻塞该操作的持有线程。如果可用,它们的调用栈会显示在视图的下半部分。

如果您对 monitor 实例有进一步的疑问,monitor 历史视图和锁定历史图形中的 Show Selection In Heap Walker 操作都提供了进入堆遍历器 (Heap Walker) 的链接,并将该 monitor 实例选作新的对象集 (object set)。

限制关注的事件

分析 monitor 事件的一个根本问题是,应用程序可能会以极高的速率生成 monitor 事件。这就是为什么 JProfiler 对等待和阻塞事件设有默认阈值,低于该阈值的事件将立即被丢弃。这些阈值在视图设置中定义,可以调高以重点关注耗时更长的事件。

对于已记录的事件,您还可以进一步应用过滤器。monitor 历史视图在视图顶部提供了阈值、事件类型和文本过滤器。锁定历史图形允许您选择感兴趣的线程或 monitor,并仅显示涉及所标记实体的锁定情况。感兴趣的事件在时间线中以不同的颜色显示,并且有一个辅助导航栏可以在这些事件之间单步切换。如果当前事件不是感兴趣的事件,您可以查看当前事件与任一方向上的下一个感兴趣事件之间相隔了多少个事件。

除了所选线程或 monitor 出现的锁定情况外,它从图形中移除时的锁定情况也会被显示。这是因为每个 monitor 事件都是由两个此类锁定情况定义的:一个在操作开始时,另一个在操作结束时。这也意味着一个完全空白的图形是一个有效的锁定情况,表示 JVM 中不再有锁。

减少需要您关注的事件数量的另一种策略是累积锁定情况。在锁定历史图形底部有一个显示所有已记录事件的时间线。在其中单击并拖动可以选择一个时间范围,并且来自所有包含事件的数据都会显示在上方的锁定图形中。在累积图形中,每个箭头可以包含多个相同类型的事件。在这种情况下,工具提示窗口将显示事件数量以及所有包含事件的总时间。工具提示窗口中的下拉列表显示时间戳,并允许您在不同事件之间切换。

死锁检测

“Current locking graph”和“Current monitors”视图基于通过 JProfiler UI 中的操作触发的“monitor 转储 (monitor dump)”运行。借助 monitor 转储,您可以检查仍在进行中的事件。这包括死锁,因为死锁是永远不会完成的事件,无法在历史视图中显示。

阻塞操作通常是短暂的,但发生死锁时,这两个视图都将持久显示该问题。此外,当前锁定图形会以红色显示产生死锁的线程和 monitor,以便您可以立即发现此类问题。

获取新的 monitor 转储将替换这两个视图中的数据。您还可以通过“Trigger monitor dump”触发器操作或通过 API 触发 monitor 转储。

Monitor 使用情况统计

为了从更高的视角调查阻塞和等待操作,monitor 统计视图 (monitor statistics view) 从 monitor 记录数据计算报告。您可以按 monitor、线程名称或 monitor 的类对 monitor 事件进行分组,并分析每行的累计计数和持续时间。