不正确地使用线程会导致多种不同类型的问题。过多的活动线程可能导致线程饥饿,线程之间可能会互相阻塞,影响应用程序的活性(liveness),或者以错误的顺序获取锁会导致死锁。此外,线程相关的信息对于调试也非常重要。
在 JProfiler 中,线程分析被分为两个视图部分:“线程”部分用于处理线程的生命周期以及捕获线程转储(thread dump)。“Monitors & locks”部分则提供了分析多线程交互的功能。
检查线程
线程历史视图会在时间线上以彩色行显示每个线程,颜色表示记录到的线程状态。线程可以按创建时间、名称或线程组排序,并且可以通过名称进行过滤。你也可以通过拖放手动调整线程的顺序。当已记录 monitor 事件时,可以将鼠标悬停在线程处于“Waiting”或“Blocked”状态的部分,查看相关的调用栈,并通过链接跳转到 monitor 历史视图。
所有线程的表格视图可在线程 monitor 视图中查看。如果在创建线程时 CPU 记录处于激活状态,JProfiler 会保存创建线程的名称并在表格中显示。在底部会显示创建线程的调用栈。出于性能考虑,不会从 JVM 请求实际的调用栈,而是使用 CPU 记录中的当前信息。这意味着调用栈只会显示满足调用树收集过滤设置的类。
如果你在配置文件设置(profiling setting)中启用了估算 CPU 时间的记录,表格中会新增一个 CPU Time 列。只有在你记录 CPU 数据时才会测量 CPU 时间。
与大多数调试器类似,JProfiler 也可以获取线程转储(thread dump)。线程转储的调用栈是 JVM 提供的完整调用栈,不依赖于 CPU 记录。你可以在选择两个线程转储后点击 Show Difference 按钮,在差异查看器中比较不同的线程转储。也可以在单个线程转储中选择两个线程,然后在上下文菜单中选择 Show Difference 进行比较。
拥有相同调用栈的线程转储会被分组。对于平台线程转储中的此类节点,可以通过上下文菜单操作显示所有相似线程。
当你分析 Java 21 或更高版本时,线程转储视图中提供了单独的“Take thread dump with virtual threads”操作。平台线程也会包含在虚拟线程转储中。
线程转储还可以通过“Trigger thread dump”触发器操作或通过 API 获取。
锁定情况分析
每个 Java 对象都有一个关联的 monitor,可用于两种同步操作:线程可以在 monitor 上等待,直到其他线程在其上发出通知,或者线程可以在 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 操作跳转到图形视图。
每个 monitor 事件都关联一个 monitor。Monitor Class 列显示使用该 monitor 的实例的类名,如果没有 Java 对象与 monitor 关联,则显示 “[raw monitor]”。无论哪种情况,monitor 都有唯一的 ID,会在单独的列中显示,便于你在多个事件中关联同一个 monitor 的使用情况。每个 monitor 事件都有一个正在执行操作的等待线程,以及可选的阻塞该操作的拥有线程。如果有,它们的调用栈会显示在视图下方。
如果你想进一步了解某个 monitor 实例,可以在 monitor 历史视图和锁定历史图中使用 Show in Heap Walker 操作,跳转到 heap walker 并将该 monitor 实例作为新的对象集选中。
限制关注的事件
分析 monitor 事件的一个基本问题是,应用程序可能会以极高的速率生成 monitor 事件。因此,JProfiler 针对等待和阻塞事件设置了默认阈值,低于该阈值的事件会被立即丢弃。这些阈值在视图设置中定义,可以提高阈值以便聚焦于持续时间更长的事件。
对于已记录的事件,你还可以进一步应用过滤器。monitor 历史视图顶部提供了阈值、事件类型和文本过滤器。锁定历史图允许你选择感兴趣的线程或 monitor,仅显示涉及已标记实体的锁定情况。感兴趣的事件会在时间线上以不同颜色显示,并有二级导航栏可逐步浏览这些事件。如果当前事件不是感兴趣的事件,你可以看到当前事件与下一个感兴趣事件之间相隔多少个事件(无论哪个方向)。
除了显示所选线程或 monitor 参与的锁定情况外,还会显示其已从图中移除时的锁定情况。这是因为每个 monitor 事件由两个这样的锁定情况定义,一个表示操作开始,另一个表示操作结束。这也意味着完全空的图形是一个有效的锁定情况,表示 JVM 中已没有锁。
另一种减少需要关注事件数量的策略是对锁定情况进行累积。在锁定历史图底部有一条时间线,显示所有已记录事件。点击并拖动可选择时间范围,所有包含在该范围内的事件数据会在上方的锁定图中展示。在累积图中,每个箭头可能包含多个相同类型的事件,此时工具提示窗口会显示事件数量以及所有事件的总耗时。工具提示窗口中的下拉列表显示时间戳,并允许你在不同事件之间切换。
死锁检测
“当前锁定图”(Current locking graph)和“当前 monitor”(Current monitors)视图基于通过 JProfiler UI 操作触发的“monitor dump”。通过 monitor dump,你可以检查仍在进行中的事件。这包括死锁,因为死锁事件永远不会结束,无法在历史视图中显示。
阻塞操作通常是短暂的,但如果发生死锁,这两个视图会永久显示该问题。此外,当前锁定图会将导致死锁的线程和 monitor 以红色显示,便于你立即发现此类问题。
获取新的 monitor dump 会替换两个视图中的数据。你也可以通过“Trigger monitor dump”触发器操作或通过 API 触发 monitor dump。
Monitor 使用统计
为了从更高的角度调查阻塞和等待操作,monitor 统计视图会根据 monitor 记录数据生成报告。你可以按 monitor、线程名称或 monitor 的类对 monitor 事件进行分组,并分析每一行的累计次数和持续时间。























