Java性能分析之线程栈详解(下)

Java性能分析之线程栈详解(下)

转载自:微信公众号“测试那点事儿”

结合jstack结果对线程状态详解

上篇文章详细介绍了线程栈的作用、状态、任何查看理解,本篇文章结合jstack工具来查看线程状态,并列出重点关注目标。Jstack是常用的排查工具,它能输出在某一个时间,Java进程中所有线程的状态,很多时候这些状态信息能给我们的排查工作带来有用的线索。 Jstack的输出中,Java线程状态主要是以下几种:

1、BLOCKED 线程在等待monitor锁(synchronized关键字)

2、TIMED_WAITING 线程在等待唤醒,但设置了时限

3、WAITING 线程在无限等待唤醒

4、RUNNABLE 线程运行中或I/O等待

下面通过详细的实例来对这几种状态进行解释

BLOCKED

如下图所示,为使用jstack工具dump线程后,查看到的线程处于blocked状态。dump线程后,最先看的是线程所处的状态。这个线程处于Blocked状态,我们需要重点分析。

首先,我们来逐条分析下jstack工具抓取到的线程信息:

jstack工具抓取到的线程信息,是从下往上分析的,由上图可见,线程先是开始运行,之后运行业务的一些方法,直到调用 org.apache.log4j.Category.forcedLog之后,开始waiting to lock。

线程的状态是:BLOCKED (on object monitor)

说明线程处于阻塞状态,正在等待一个monitor lock。阻塞原因是:因为本线程与其他线程公用了一个锁,这时,已经有其他在线程正在使用这个锁进入某个synchronized同步方法块或者方法。当本线程想要进入这个同步代码块时,也需要这个锁,但锁已被占用,从而导致本线程处于阻塞状态。

第一行中包含了线程名和id等信息,如上图中的"druid-consumer-pool-3",nid(每个线程都有线程pid,将该pid转成16进制的值,即为jstack结果中的nid,可以通过nid唯一确认一个线程。)

第一行中还有线程目前正在  waiting for monitor entry,还是表明了线程在等待进入monitor。

Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”。目前线程状态为:waiting for monitor entry,说明它是“Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。

这时有两种可能性:

1、该 monitor不被其它线程拥有, Entry Set里面也没有其它等待线程。本线程即成为相应类或者对象的 Monitor的 Owner,执行临界区的代码

2、该 monitor被其它线程拥有,本线程在 Entry Set队列中等待。

在第一种情况下,线程将处于 “Runnable”的状态

而第二种情况下,线程 DUMP会显示处于 “waiting for monitor entry”

根据以上分析,我们可以看出,线程想要调用log4j,目的是打印日志,但是由于调用log4j写日志有锁机制,于是线程被阻塞了。再排查项目使用的log4j版本,得知此版本存在性能bug,优化手段为升级log4j版本或者调整日志级别、优化日志打印的内容,或者添加缓存。

 waiting to lock <地址>

说明线程使用synchronized申请对象锁未成功,于是开始等待别的线程释放锁。线程在监视器的进入区等待。这条一般在调用栈顶出现,线程状态一般对应为Blocked。

TIMED_WAITING

如下图所示,为使用jstack工具dump线程后,查看到的线程处于TIMED_WAITING状态。

线程的状态是:TIMED_WAITING

这时的线程处于sleep状态,说明线程在有时限的等待另一个线程的特定操作,一般会有超时时间唤醒。就一般情况来说,出现TIMED_WAITING很正常,等待网络IO等都会出现这种状态,但是大量的线程处于TIMED_WAITING时,需要我们重点分析。

第一行中,显示线程在waiting on condition,这说明线程在等待某个条件的发生,从而自己唤醒,或者是调用了 sleep(n)。

当线程在waiting on condition时,线程状态可能为:

1、java.lang.Thread.State: WAITING (parking):一直等某个条件发生;

2、java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时等待某个条件发生,即使这个条件不到来,也将定时唤醒自己。

在我们这个例子里,线程处于 TIMED_WAITING状态。

parking to wait for <地址>目标

这里即为第一行“waiting on condition" 所等待的条件,等待是java.util.concurrent.CountDownLatch$Sync,这是一种闭锁的实现,是一种同步工具类,可以延迟线程的进度直到闭锁到达终止状态,其内部包含一个计数器,该计数器被初始化为一个整数,表示需要等待事件的数量。由以上分析可以知道,线程是因为向druid写数据,由于有同步机制,而进入TIMED_WAITING状态。

和上个例子线程在parking to wait for 不同,在这个例子中,线程也是处于TIMED_WAITING状态,但是第一行中显示线程正在 in Object.wait(),第四行显示线程waiting on <地址> 目标。

线程在in Object.wait(), 说明线程在获得了监视器之后,又调用了 java.lang.Object.wait() 方法。

上篇线程详解(一)中说过等待monitor 的线程分为两种

在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”

在 “Wait Set”中等待的线程状态是 “in Object.wait()”

本例是在“Wait Set”中等待的线程,其状态是in Object.wait(),这说明线程获得了 Monitor,但是线程继续运行的条件没有满足,则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。

此时线程状态大致为以下几种:

1、java.lang.Thread.State: TIMED_WAITING (on object monitor);

2、java.lang.Thread.State: WAITING (on object monitor);

本例中线程就处于TIMED_WAITING状态。

WAITING

如下图所示,为使用jstack工具dump线程后,查看到的线程处于WAITING状态。

(1)线程的状态是:WAITING

意思就是线程在等待另外一个线程去解除它的等待状态。一个典型的例子就是生产者消费者模型,当生产者生产太慢的时候,消费者要等待生产者生产才能去消费,这段时间消费者线程就处于waiting状态。还可以使用lock.wait()方法使线程进入waiting状态,无超时的等待,必须等待lock.notify()或lock.notifyAll()或接收到interrupt信号才能退出等待状态。

(2)parking to wait for <地址> 目标

第一行中,显示线程在waiting on condition,这说明线程在等待某个条件的发生,从而自己唤醒。

当线程在waiting on condition时,线程状态可能为

java.lang.Thread.State: WAITING (parking):一直等某个条件发生;

java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时等待某个条件发生,即使这个条件不到来,也将定时唤醒自己。

在这个例子里,线程处于 WAITING状态,parking to wait for所等待的是java.util.concurrent.locks.AbstractQueuedSynchronizer,这也是java实现同步机制。

RUNNABLE

如下图所示,为使用jstack工具dump线程后,查看到的线程处于RUNNABLE 状态。

在这个例子里,可以清楚看到整个线程运行的过程。在线程运行过程中,有很多次获取锁,即为上图中locked <地址> 目标,即此线程使用synchronized申请对象锁成功,是监视器的拥有者,可以在临界区内进行操作。上图所lock的内容有java IO的输入输出流等。

"02‘

在一次测试过程中,通过线程打印有了一个意外收获

如下面信息,“http-bio-18272-exec-258”,表示Tomcat 的启动模式为 bio模式,将bio模式改为nio模式,在该项目中,其他条件不变,只将bio模式更改为nio模式,tps提升了一倍

tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.或者登录他们的默认页面http://localhost:8080/查看其中的服务器状态。

1)bio :默认的模式,性能非常低下,没有经过任何优化处理和支持.

2)nio :利用java的异步io护理技术,no blocking IO技术.

想运行在该模式下,直接修改server.xml里的Connector节点,修改protocol为

<Connector port="80" protocol="org.apache.coyote.http11.Http11NioProtocol"connectionTimeout="20000" URIEncoding="UTF-8" useBodyEncodingForURI="true"enableLookups="false" redirectPort="8443" />

启动后,就可以生效。

3)apr

安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.

必须要安装apr和native,直接启动就支持apr。

想获取更多测试技能,欢迎加入BestTest5000人交流群:435092293

原文地址:https://www.cnblogs.com/lyftest/p/8194868.html

时间: 2024-10-29 19:05:37

Java性能分析之线程栈详解(下)的相关文章

Java内存分析利器MAT使用详解

这是一篇阅读MAT helper的笔记.Heap dump是java进程在特定时间的一个内存快照.通常在触发heap dump之前会进行一次full gc,这样dump出来的内容就包含的是被gc后的对象. dump文件包含的内容: 1,全部的对象:类,域,原生值和引用: 2,全部的类:classloader,类名,超类,静态域: 3,GC root:被JVM定义的可触达的对象: 4,线程栈和本地变量:线程的call stack,本地对象每帧的信息. dump文件不包含内存的分配信息,因此无法查询

关于Java中进程和线程的详解

一.进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命 周期.它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消.反映了一个程序在 一定的数据 集上运行的全部动态过程.通过进程控制块(PCB)唯一的标识某个进程.同时进程占据着相应的资源(例如包 括cpu的使用 ,轮转时间以及一些其它设备的权限).是系统进行资源分配和调度的一个独立单位. 程序和进程之间的主要区别在于: 状态         是否具有资源

Java高并发之线程池详解

线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升. 另外一个好处是可以设定池化对象的上限, 例如预防创建线程数量过多导致系统崩溃的场景. jdk中的线程池 下文主要从以下几个角度讲解: 创建线程池 提交任务 潜在宕机风险 线程池大小配置 自定义阻塞队列BlockingQueue 回调接口 自定义拒绝策略 自定义ThreadFactory 关闭线程池

“全栈2019”Java异常第二十章:自定义异常详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异常第二十章:自定义异常详解 下一章 "全栈2019"Java异常第二十一章:finally不被执行的情况 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学

Java线程池详解(二)

一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉得还有太多内容需要补充,每次都是修修补补,总觉得还缺点什么.在第一篇中,我着重描述了java线程池的原理以及它的实现,主要的点在于它是如何工作的.而本文的内容将更为上层,重点在于如何应用java线程池,算是对第一篇文章的一点补充,这样对于java线程池的学习和总结稍微完整一些. 使用过java线程池

“全栈2019”Java第二十八章:数组详解(上篇)

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第二十八章:数组详解(上篇) 下一章 "全栈2019"Java第二十九章:数组详解(中篇) 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学习小组"

“全栈2019”Java第三十章:数组详解(下篇)

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第三十章:数组详解(下篇) 下一章 "全栈2019"Java第三十一章:二维数组和多维数组详解 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学习小组&qu

Java 线程(多线程)详解

查看了许多书籍,网上的博客,现在我来说一下有关于我对线程的详解,有不对的欢迎指正. 一. 线程的生命周期: 程序有自己的一个生命周期,线程也不例外,也有自己的生命周期.查看许多书籍或者网上资料,发现了一件很有趣的事情,那就是它们对线程的生命周期不是唯一.有两种或者以上的线程生命周期. 第一种线程生命周期线程状态转换图:一共5个状态:新建,就绪,运行,阻塞和结束   图 1 第二种生命周期图:一共6个状态:New,Runnable,Blocked,Waiting,Timed Waiting,Ter

Java并发编程之---Lock框架详解

Java 并发开发:Lock 框架详解 摘要: 我们已经知道,synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等.Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题.本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念. 一