死锁排查与Tomcat深度调优

故障排查与优化深入专题

一,案例故障描述

1.1 具体现象
这是不久前的一个客户案例,客户的一个门户网站系统是基于java开发的,运行多年,一直正常,而最近经常罢工,频繁出现java进程占用CPU资源很高的情况,在CPU资源占用很高的时候,web系统响应缓慢,下图是某时刻服务器的一个状态截图:
在这里插入图片描述
下面是htop获取的状态信息:
在这里插入图片描述
从图中可以看出,java进程占用CPU资源达到300%以上,而每个CPU核资源占用也比较高,都在30%左右,让客户的运维人员检查后,也没发现什么异常,于是就把问题抛给了程序方面,而研发查看了代码,也没发现什么异常情况,最后又推给运维了,说是系统或者网络问题,研发说正常情况下java进程占用CPU不会超过100%,而这个系统达到了300%多,肯定是系统有问题,然而运维也无计可施了,最后,急中生智,重启了系统,java进程占用的CPU资源一下子就下来了。

就这样,重启成了运维解决问题的唯一办法。

然而,这个重启的办法,用了几天就不行了,今天,又例行重启了系统,但是重启后,web系统仅能正常维持30分钟左右,接着,彻底无法访问了,现象还是java占用CPU大量资源。

1.2 问题分析
从这个案例现象来看,问题应该出在程序方面,原因如下:

java进程占用资源过高,可能是在做GC,也可能是内部程序出现死锁,这个需要进一步排查。
网站故障的时候,访问量并不高,也没有其他并发请求,攻击也排除了(内网应用),所以肯定不是系统资源不足导致的。
java占用CPU资源超过100%完全可能的,因为java是支持多线程的,每个内核都在工作,而java进程持续占用大量CPU资源就不正常了,具体原因要进一步排查。
检查发现,web系统使用的是tomcat服务器,并且tomcat配置未作任何优化,因此这个需要配置一些优化参数。
针对上面四个原因,那么下面就具体分析下,如何对这个Web系统进程故障分析和调优。

二,java中进程与线程的概念

要了解java占用CPU资源超过100%的情况,就需要知道进程和线程的概念和关系。
进程是程序的一次动态执行,它对应从代码加载,执行至完毕的一个完整的过程,是一个动态的实体,它有自己的生命周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤销。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
进程和线程的关系:

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  2. 进程作为资源分配的最小单位,资源是分配给进程的,同一进程的所有线程共享该进程的所有资源。
  3. 真正在处理机上运行的是线程。

进程与线程的区别:

调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
系统开销:在创建或撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤销线程时的开销。
Tomcat底层是通过JVM运行的,JVM在操作系统中是作为一个进程存在的,而java中的所有线程在JVM进程中,但是CPU调度的是进程中的线程。因此,java是支持多线程的,体现在操作系统中,就是java进程可以使用CPU的多核资源。那么java进程占用CPU资源300%以上是完全可能的。

三,排查java进程占用CPU过高的思路

下面重点来了,如何有效的去排查java进程占用CPU过高呢,下面给出具体的操作思路和方法。

3.1 提取占用CPU过高的进程
提取占用CPU高进程的方法很多,常用的方法有如下两个:

方法一:使用top或htop命令查找到占用CPU高得进程的pid
top -d 1

方法二:使用ps查找到tomcat运行的进程
ps -ef | grep tomcat

3.2 定位有问题的线程的pid
在Linux中,程序中创建的线程(也称为轻量级进程,LWP)会具有和程序的PID相同的”线程组ID”。同时,各个线程会获得其自身的线程ID(TID)。对于Linux内核调度器而言,线程不过是恰好共享特定资源的标准的进程而已。

那么如何查看进行对应的线程信息呢,方法很多,常用的命令有ps,top和htop命令

3.2.1 ps命令查看进程的线程信息
在ps命令中,”-T”选项可以开启线程查看。下面的命令列出了由进程号为的进程创建的所有线程。

ps -T -p pid
例如:
在这里插入图片描述
在输出中,”SPID”栏表示线程ID,而”CMD”栏则显示了线程名称。
使用ps命令的一个缺点是,无法动态查看每个线程消耗资源的情况,仅仅能看线程id信息,所以我们更多使用的是top和htop

3.2.2 top命令获取线程信息
top命令可以实时显示各个线程情况。要在top输出中开启线程查看,可调用top命令的”-H”选项,该选项会列出所有Linux线程。看下图:
在这里插入图片描述
要让top输出某个特定进程并检查该进程内的线程情况,可执行如下命令
在这里插入图片描述
可以看到,每个线程状态是实时刷新的,这样我们就可以观察,哪个线程消耗CPU资源最多,然后把它的SPID(TID)记录下来。

3.2.3 htop命令获取线程信息
通过htop命令查看单个进程的线程信息,更加简单和友好,此命令可以在树状视图中监控单个独立线程。
要在htop中启用线程查看,可先执行htop,然后按F2键来进入htop的设置菜单。选择”设置”栏下面的”显示选项”,然后开启”树状视图”和”显示自定义线程名”选项。最后按F10键退出设置。如下图所示:
在这里插入图片描述
在这里插入图片描述

很简单的把,这样就可以清楚地看到单个进程的线程视图了,并且状态信息也是实时刷新的。通过这个方法也可以查找出CPU利用率最厉害的线程号。然后记录下来。

3.3 将线程的pid转换为16进制数
printf ‘%x\n’ pid

注意,此处的SPID(TID)为上一步找到的占CPU高的线程号

3.4 使用jstack工具将进程信息打印输出
jstack是java虚拟机自带的一种堆栈跟踪工具。可以用于生成java虚拟机当前时刻的线程快照。
线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
总结一句话:jstack命令主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)。

想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态

线程堆栈信息可能会看到的线程的几种状态
NEW:未启动的。不会出现在Dump中
RUNNABLE:在虚拟机内执行的。运行状态,可能里面还能看到locked字样,表明它获得了某把锁
BLOCKED:受阻塞并等待监视器锁。被某个锁(synchronizers)给block住了。
WAITING:无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(),wait(),sleep(),join()等语句里。
TIMED_WATING:有时限的等待另一个线程的特定操作。和WAITING的区别是wait()等语句加上了时间限制wait(timeout)
TERMINATED:已退出的
那么怎么去使用jstack呢,很简单,用jstack打印线程信息,将信息重定向到文件中,可执行如下操作:

jstack pid | grep spid(tid)

例如:
在这里插入图片描述
这里面重点关注下输出的线程状态,如果有异常,会输出相关异常信息或者跟程序相关的信息,将这些信息给开发人员,就可以马上定位CPU过高的问题,所以这个方法非常有效。

3.5 根据输出信息进行具体分析
学会了怎么使用jstack命令之后,我们就可以看看,如何使用jstack分析死锁,这也是我们一定要掌握的内容。什么是死锁?所谓死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外界因素作用,它们都将无法执行下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

最后,我们使用jstack来看一下线程堆栈信息:
在这里插入图片描述

四,tomcat配置调优

在实际工作中接触过很多线上基于java的tomcat应用案例,多多少少都会出现一些性能问题,在追查原因的时候发现,tomcat的配置都是默认的,没有经过任何修改和调优,这肯定会出现性能问题了,在tomcat默认配置中,很多参数都设置的很低,尤其是内存和线程的配置,这些默认配置在web没有大量业务请求时,不会出现问题,而一旦业务量增长,很容易成为性能瓶颈。

tomcat的调优,主要是从三个方面进行:
内存
并发
缓存
4.1 内存调优
这个主要是配置tomcat对JVM参数的设置,我们可以在tomcat的启动脚本catalina.sh中设置java_OPTS参数。

JAVA_OPTS参数常用的有如下几个:
-server:表示启用jdk的server运行模式
Xms:设置JVM初始堆内存的大小
Xmx:设置JVM最大堆内存的大小
XX:PermSize:设置堆内存持久代初始值大小
XX:MaxPermSize:设置持久代最大值
Xmn1g:设置堆内存年轻代大小
JVM的大小设置,跟服务器的物理内存有直接关系,不能太小,也不能太大,如果服务器内存为32G,可以采用以下配置:

JAVA_OPTS=’-server -Xms8192m -Xmx8192m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=512m’
对于堆内存大小的设置有如下经验:

将最小堆大小(Xms)和最大堆大小(Xmx)设置为彼此相等;
堆内存不能设置过大,虽然堆内存越大,JVM可用的内存就越多。
但请注意,太多的堆内存可能会使垃圾收集长时间处于暂停状态。
将Xmx设置为不超过物理内存的50%,最大不超过32GB
4.2 Tomcat并发优化与缓存优化
这部分主要是对Tomcat配置文件server.xml内的参数进行的优化和配置。默认的server.xml文件一些性能参数配置很低,无法达到tomcat最高性能,因此需要有针对性的修改一下,常用的tomcat优化参数如下:
在这里插入图片描述

五,Tomcat Connector三种运行模式(BIO,NIO,APR)比较与优化

5.1 什么是BIO,NIO,APR
BIO(blocking I/O):即阻塞式I/O操作的java API;
Tomcat7以下版本默认是BIO模式,由于每个请求都要创建一个线程来处理,线程开销较大,不能处理高并发的场景,在三种模式中性能最低
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,因此,当并发量高时,线程数会较多,造成资源浪费。
NIO(non-blocking I/O):基于缓冲区,并能提供非阻塞I/O操作的java API;
Tomcat8及以上默认是NIO模式,比BIO拥有更好的并发运行性能。
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(Apache Portable Runtime I/O):Apache可移植运行时I/O,是Apache HTTP服务器的支持库
Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大地提高Tomcat对静态文件的处理性能。
Tomcat APR也是tomcat上运行高并发应用的首选模式
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,可以看出,AIO是从操作系统级别来解决异步I/O问题,因此可以大幅度提高性能。
从这个运行模式中,我们基本可以看出,三种模式的优劣了,下面总结三种模式的特点和使用环境

BIO:已经淘汰,JDK1.4以前的唯一选择
NIO:适用于连接数目多且连接比较短的架构,比如消息通信服务器,并发局限于应用中,编程比较复杂,JDK1.4之后支持
AIO:用于连接数目多且长连接的架构中,比如直播服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持
5.2 tomcat中如何使用BIO,NIO,APR模式
这里以tomcat8.5.29为例进行介绍,在tomcat8版本中,默认使用的就是模式,也就是无需做任何配置,Tomcat启动的时候,可以通过catalina.out文件看到Connector使用的是哪一种运行模式,默认情况下会输出如下日志信息:
在这里插入图片描述
这个日志表面目前tomcat使用的是NIO模式

要让tomcat运行在APR默认的话,首先需要安装apr,apr-utils,tomcat-native等依赖包,简单安装过程如下:
在这里插入图片描述

最后,重启tomcat,使配置生效。Tomcat重启过程中,可以通过catalina.out文件看到Connector使用的是哪一种运行模式,如果能看到类似下面的输出,表示配置成功。
在这里插入图片描述
5.3 tomcat三种模式测试
在这里插入图片描述

通过测试,随着线程的不断增多,bio模式性能越来越差,就算是在本地,错误率和响应时间都在明显增加,而吞吐量样本数和每秒传输速率都在下降;
而Nio和Aio模式基本上没有变化太多,都保持在一个稳定的状态;
因此,生产环境tomcat到底要用什么模式,具体的服务器配置还需要进行具体的压力测试。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值