线程转储分析是用于分析基于Java的应用程序中的性能瓶颈的传统方法。在现代,我们有APM工具,可以提供各种指标和屏幕来深入研究和识别性能问题,甚至在代码级别也是如此。但对于某些性能问题或场合,线程转储分析仍然是识别瓶颈的最佳方法。
何时使用线程转储
要分析任何性能问题,最好在1到2秒的时间间隔内进行一系列线程转储。每隔1-2秒进行10-15次线程转储,有助于分析陷入困境的线程或跨线程转储执行相同的代码。
l 线程转储可以在以下情况下进行:
l 应用程序挂起且没有响应
l 应用程序需要时间才能响应
l 运行应用程序的服务器上CPU使用率高
l 活动线程数或线程总数的增加
线程转储有时也由应用程序服务器自动生成。例如,WebSphere应用程序服务器在OutOfMemoryError情况下生成线程转储,这有助于分析线程在该时刻的各种状态。
对于场景#1和场景2,应该将重点放在处于阻塞、停放/等待和可运行状态的线程上。对于场景#3,重点应该放在处于可运行状态的线程上。无限循环执行中的一些线程可能会导致高CPU使用率,查看可运行状态可能有助于发现这一点。对于场景#4,应该将重点放在处于可运行状态和驻留/等待线程状态的线程上。在所有场景中,忽略处于暂停或定时等待状态的线程,这些线程正在等待执行任务/请求。
分析工具使用
使用工具分析线程转储将提供有关线程及其状态的许多统计信息。然而,有时它可能无法揭示系统中的真正瓶颈。手动处理线程转储并通过Notepad++等工具进行分析总是更好的。如果要分析许多线程转储,则可以使用IBM线程转储分析器等工具。在有组织的视图中查看线程转储有助于加快分析过程。虽然它不会像在线分析工具那样提供许多复杂的统计数据,但它可以帮助更好地可视化线程转储,提供一个视图来查看由于另一个线程而被阻塞的线程,还可以帮助比较线程转储。
在分析线程转储时,了解线程转储是针对哪个应用程序服务器进行的非常重要,因为这将有助于集中精力分析正确的线程。例如,如果在WebSphere应用程序服务器上进行了线程转储,那么“Web容器”线程池应该是第一个开始分析的地方,因为它是WebSphere应用程序的入口点,它将开始为到达它的请求提供服务。
线程转储类型
通常,在线程转储中会有两种类型的线程。一类线程与应用程序相关,有助于执行应用程序代码。另一类是将执行操作的线程,如从网络读取/写入、心跳检查和各种其他操作,包括JVM内部(如GC)等。根据问题的不同,应将重点放在这两类线程上。大多数时候,应用程序代码可能是造成性能瓶颈的罪魁祸首;因此,应该更多地关注应用程序线程。
线程池
线程转储显示应用程序中可用的各种线程池。在WebSphere应用程序服务器中,名为“WebContainer:<id>”的线程属于WebSphere线程池。计算此类线程的数量应等于定义的线程池大小。如果超过,则表示线程池中存在线程泄漏。需要验证线程转储中不同线程池的大小。
ForkJoinPool是Java CompletableFuture用来异步运行任务的另一个线程池。如果此池中有太多异步任务,则需要增加池的大小,或者需要创建另一个更大的池。否则,此ForkJoinPool将成为异步任务执行的瓶颈。
如果应用程序正在使用Java 执行框架创建线程池,那么将为这些线程提供默认名称“pool-<id1>-thread-<id2>”。这里“id1”表示线程池编号,“id2”表示线程库中的线程数。有时,如果开发人员每次都创建新的线程池,而没有通过执行框架关闭它们,那么它每次都会创建不同的池,线程数量也会增加。如果线程没有主动执行某件事,这可能不会造成问题,但会导致OutOfMemoryError,即无法通过达到线程创建的最大数量来创建新线程。在分析任何线程转储时,查看不同的线程池并确保所有线程池都在定义/预期的限制内总是很好的。
应用方法
关注线程转储的堆栈跟踪中的应用程序方法有助于分析应用程序代码中的问题。如果应用程序中有同步的代码或块,那么应用程序线程将等待获取对象上的锁,以进入特定的代码/块执行。这将是昂贵的,因为通过让其他线程等待,只允许一个线程进入代码执行。这种情况可以在线程转储中看到,在线程转储中,线程等待获取对象的锁。如果不需要,可以修改代码以避免这种同步。
结论
线程转储包含关于虚拟机、JVM参数、内存、GC相关信息、运行它的硬件等等。始终建议查看这些可能有助于分析的详细信息。