使用DHC工具发送请求,Nginx能够正常接到请求并转发,但是tomcat中的日志一直没有打印出来,说明Nginx是正常的,是tomcat没有响应请求,说明此时tomcat处于无法访问(假死)状态。
因为个人对tomcat不熟悉,所以网上找了下tomcat假死的原因,大致有如下几种:
与tomcat连接未关闭/长连接数超过最大连接数
按顺序一个一个来排查:
ps –ef |grep tomcat,先找到tomcat对应的进程号
jstack –l 进程号 > tomcat.stack
然后使用vim找了下“DEADLOCK”,没有找到,应该是没有出现死锁(最好dump多次,才能更好的定位问题)
下面的图是我在出现内存溢出后截的:
先把heap信息导出来:
jmap –dump:format=b,file=tomcat.bin tomcat进程号,一共有20G…
使用mat工具分析:(mat网上随便下吧,我想传上去骗点分,老是显示资源已经有了…)
vim MemoryAnalyzer.ini 一般将-Xmx修改为文件大小的两倍
./ParseHeapDump.sh ../tomcat.bin org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
会出来这么三个文件,里面都是html网页,记录了堆信息。
主要是看TopConsumer,这里记录了内存消耗大户:
这里判断那里内存溢出,需要结合代码以及堆栈文件了。结合工程的逻辑,这里一看就知道是多线程的问题,线程太多并且每个线程要拉取的HBase数据太多造成JVM内存溢出。
虽然dump文件中没有死锁,但是看懂dump文件对于了解程序运行状态还是有帮助的,所以这里介绍下dump文件中的内容。如果有说的不对的地方,麻烦大家及时指正下呀!
如果问题不好定位的话,需要dump多次,每次间隔个几秒钟,然后结合着一起看。
Dump文件结构大致如下:
可以看到一个JVM进行在运行时有很多个部分在一起协同合作,主要关心我们自己编写的程序部分的堆栈信息就可以了,就是上面的JAVA EE部分。(具体信息详见“参考3”,忽略上面的weblogic.Server.main部分,那是参考文章作者自己的内容)。
来看下JAVA EE这部分的内容,从堆栈文件中可以看到线程会显示两种状态,一种是操作系统线程状态,一种是JVM线程状态(在Thread.State枚举类中有说明):
为什么会有两种状态呢?因为JVM是跨平台的,Linux下有Linux下的状态,Windows下有Windows下的状态,JVM为了方便表示将它们统一到JVM定义的六种状态中来(在java.lang.Thread.State类中)。接下来分别细说下JVM线程状态。
NEW:
线程刚创建且未启动
RUNNABLE:
线程处于运行状态
BLOCKED:
线程等待获取监视器锁(monitor lock)以期进入同步代码块/方法(即等待锁,并且是synchronized)中。(下面会说到Monitor中的Entry Set和Wait Set,BLOCKED还有下面即将说到的WAITING和TIMED_WAITING都和这两个队列有关)
WAITING:
线程无期限的等待另外一个线程来执行一个特定的操作,等待另外一个线程调用notify()/notifyAll()。特定操作可以是:Object.wait()、Thread.join()、LockSupport.park()
TIMED_WAITING:
线程在一段时间范围内等待另外一个线程来执行特定操作,时间到了之后又回到运行状态。特定操作可以是:Object.wait(time)、Thread.join(time)、LockSupport.park(time)、Thread.sleep(time)
Ps1: sleep可以在任何地方使用, wait、notify、notifyAll只能在同步控制方法或同步控制块中使用;sleep不会释放锁,但是wait会;结合以上两点,WAITING状态结束之后会转为BLOCKED状态,因为它是要重新取获取锁的。
Ps2:ReentrantLcok中未获取到锁时调用的是park/parkNanos/(进入定义的队列),所以用ReentrantLock是进入到WAITING/TIMED_WAITING状态。
TERMINATED:
线程运行结束
Entry Set & Wait Set介绍:
每个Java对象有且仅有一个Moniter,Entry Set和Wait Set属于这个Moniter的两个队列,又可以称为锁池和等待队列,他们是Java中用以实现线程之间的互斥与协作的主要手段,可以看成是对象或者class的锁。等待队列中的线程不能去争取锁,只有晋升到锁池队列中的线程才能去争取锁,这个就是两者的区别。如果对象调用的是notify,那么只有一个线程能进入到锁池队列,如果调用的是notifyAll,所有线程才能全部进到锁池队列中一起争夺锁。
实际一些日志中的信息分析:
BLOCKED状态:
正在等待获取锁0x0000***188
CPU占用高,load高,响应很慢:
一个请求过程中多次dump,比如runnable线程,如果一直在执行同一个方法,说明可能有问题了(while死循环)
看我之前那篇定位CPU高的博客《Linux CPU飙升到了100%怎么排查》
CPU占用不高,响应慢:
Dump多次
BLOCKED状态的越来越多,很有可能被一个大锁锁住了
WAIT状态越来越多,那么可能是线程停在了I/O,数据库连接或者网络连接等地方
或者直接就是两者很多
死锁:
死锁变现为程序不再响应用户请求或者程序的停顿,dump文件中显示有deadlock或者有两个线程在相互wait(这种我还没遇到过,但是有大神遇到了,截个图分别以后查看)
参考:
https://www.javatang.com/archives/2017/10/25/36441958.html(Dump文件如何分析)
https://www.jianshu.com/p/8fe0b117cacd(Java线程状态和系统线程状态区别)
https://www.oschina.net/translate/jvm-how-to-analyze-thread-dump?print(dump文件结构图)
https://www.javatang.com/archives/2017/10/26/08572060.html(dump文件一些典型案例)