ps -mp pid -o THREAD,tid,time;printf "%x\n" tid;jstack pid |grep tid -A 30;CPU占用太高追查,java
http://datoumao.blogspot.com/2012/09/linuxcputhread.html
ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu
线上应用故障排查之一:高CPU占用
一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环。
以我们最近出现的一个实际故障为例,介绍怎么定位和解决这类问题。
根据top命令,发现PID为28555的Java进程占用CPU高达200%,出现故障。
通过ps aux | grep PID命令,可以进一步确定是tomcat进程出现了问题。但是,怎么定位到具体线程或者代码呢?
首先显示线程列表:
ps -mp pid -o THREAD,tid,time
找到了耗时最高的线程28802,占用CPU时间快两个小时了!
其次将需要的线程ID转换为16进制格式:
printf "%x\n" tid
最后打印线程的堆栈信息:
jstack pid |grep tid -A 30
找到出现问题的代码了!
现在来分析下具体的代码:ShortSocketIO.readBytes(ShortSocketIO.java:106)
ShortSocketIO是应用封装的一个用短连接Socket通信的工具类。readBytes函数的代码如下:
public byte[] readBytes(int length) throws IOException {
if ((this.socket == null) || (!this.socket.isConnected())) {
throw new IOException("++++ attempting to read from closed socket");
}
byte[] result = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
if (this.recIndex >= length) {
bos.write(this.recBuf, 0, length);
byte[] newBuf = new byte[this.recBufSize];
if (this.recIndex > length) {
System.arraycopy(this.recBuf, length, newBuf, 0, this.recIndex - length);
}
this.recBuf = newBuf;
this.recIndex -= length;
} else {
int totalread = length;
if (this.recIndex > 0) {
totalread -= this.recIndex;
bos.write(this.recBuf, 0, this.recIndex);
this.recBuf = new byte[this.recBufSize];
this.recIndex = 0;
}
int readCount = 0;
while (totalread > 0) {
if ((readCount = this.in.read(this.recBuf)) > 0) {
if (totalread > readCount) {
bos.write(this.recBuf, 0, readCount);
this.recBuf = new byte[this.recBufSize];
this.recIndex = 0;
} else {
bos.write(this.recBuf, 0, totalread);
byte[] newBuf = new byte[this.recBufSize];
System.arraycopy(this.recBuf, totalread, newBuf, 0, readCount - totalread);
this.recBuf = newBuf;
this.recIndex = (readCount - totalread);
}
totalread -= readCount;
}
}
}
问题就出在标红的代码部分。如果this.in.read()返回的数据小于等于0时,循环就一直进行下去了。而这种情况在网络拥塞的时候是可能发生的。
至于具体怎么修改就看业务逻辑应该怎么对待这种特殊情况了。
最后,总结下排查CPU故障的方法和技巧有哪些:
1、top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。
2、PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。
3、jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
4、pstack:Linux命令。可以查看某个进程的当前线程栈运行情况。
开门见山,本文将简述如何使用java thread dump来分析CPU高使用率以及线程死锁问题。
一般java thread dump用于web开发中分析web容器或是应用服务器的性能问题还是比较常用并有效的。常用的入门级web容器Tomcat,以及高级别的jboss、websphere、weblogic等的性能调优问题都可以使用java thread dump来分析。
首先,阐述一下thread dump常用来解决的是何种问题
(1)高CPU使用
(2)线程死锁
其次,使用步骤[以JBOSS为例]
1..get thread dump log
(1)找到应用程序所在的进程号,命令如下
1. ps aux |grep 'jboss' | grep 'java'
、
获取需要的PID
(2)执行sudo kill -3 PID获取thread dump log(PID是第一步获取)。
注意:在不同的linux环境下执行输出的日志的地方可能不同。在IBM的PowerPC小型机上的linux上执行kill -3 pid会在工作目录下产生类似javacore.20100409.161739.7614.0001.txt的文件。JBOSS默认环境下,thread dump log输出到jboss console,所以thread dump信息会输出到个人定义的控制台打印log中。
部分示例如下所以:
引用
2010-10-08 20:27:42
Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
"http-182.50.0.138-8084-6" daemon prio=10 tid=0x08ce5000 nid=0x6a4c in Object.wait() [0x87b5c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x95eb81b0> (a org.apache.tomcat.util.net.JIoEndpoint$Worker)
at java.lang.Object.wait(Object.java:485)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.await(JIoEndpoint.java:415)
- locked <0x95eb81b0> (a org.apache.tomcat.util.net.JIoEndpoint$Worker)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:441)
at java.lang.Thread.run(Thread.java:619)
"http-182.50.0.138-8084-5" daemon prio=10 tid=0x08c2e000 nid=0x6a4b in Object.wait() [0x87bad000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x95ed0600> (a org.apache.tomcat.util.net.JIoEndpoint$Worker)
at java.lang.Object.wait(Object.java:485)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.await(JIoEndpoint.java:415)
- locked <0x95ed0600> (a org.apache.tomcat.util.net.JIoEndpoint$Worker)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:441)
at java.lang.Thread.run(Thread.java:619)
"ajp-127.0.0.1-8009-Acceptor-0" daemon prio=10 tid=0x894de800 nid=0x6a45 runnable [0x881f3000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:390)
- locked <0x949c1288> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:453)
at java.net.ServerSocket.accept(ServerSocket.java:421)
at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:61)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:309)
at java.lang.Thread.run(Thread.java:619)
DefaultQuartzScheduler_QuartzSchedulerThread" prio=10 tid=0x8a460800 nid=0x6a38 sleeping[0x88818000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:394)
(3)获取线程信息
使用上面的ps或者使用top命令也可以。获取的线程信息如下所示:
引用
27143 root 20 0 780m 376m 11m S 17 11.5 2:56.48 java
4839 root 20 0 778m 162m 11m S 10 5.0 1717:03 java
5049 root 20 0 764m 147m 11m S 4 4.5 1744:06 java
1 root 20 0 2100 720 624 S 0 0.0 0:28.08 init
2 root 15 -5 0 0 0 S 0 0.0 0:00.00 kthreadd
3 root RT -5 0 0 0 S 0 0.0 0:00.44 migration/0
第一列是十进制PID,需要转化为16进制后才能和thread dump信息对应。
2.分析thread dump信息[不在列举示例,只讲思想]
(1)分析高CPU使用线程的thread dump信息,查找那些代码导致高CPU使用。
(2)线程死锁
a.为了发现线程动态变化,需要多次做thread dump,每次间隔10-30s为佳.
b.线程状态用 runnable(正在运行)、waiting for monitor(主动等待)、waiting for monitor entry(死锁)。所以我们最多的是关注runnable和entry类型的线程。
一种典型的死锁是在server端多个应用同时使用同一个jboss资源,这时候需要将多个应用分不到不用的队列中。
参考文献:
http://www.51testing.com/?uid-188107-action-viewspace-itemid-226468
1.分析内存的工具
Eclipse Memory Analyzer Tool(俗称MAT),下载地址为: http://www.eclipse.org/mat/
使用Memory Analyzer tool(MAT)分析内存泄漏(一)
使用Memory Analyzer tool(MAT)分析内存泄漏(二) 使用前需要在linux上通过jmap -dump:format=b,file={$filename} ${pid}方式将heap的内存快照文件给dump出来,然后就可以通过上面的MAT进行分析了。注意dump出来的文件名要以bin作为后缀名不然可能识别不了哦.
例如: jmap -dump:format=b,file=a.bin 2298
2.线程状态分析
"exec-613" Id=713 in BLOCKED on lock=com.ss.nio.ClientFactory@2262ce5f owned by tomcatThreadPool-exec-553 Id=623
"exec-553" Id=623 in TIMED_WAITING on lock=com.ss.nio.AbstractRequest@35ce75e at java.lang.Object.wait(Native Method)
"NioProcessor-1" Id=700 in RUNNABLE (running in native) at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:215) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65) at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
"RMI TCP Connection(8)-172.25.3.81" Id=698 in RUNNABLE at sun.management.ThreadImpl.getThreadInfo0(Native Method) at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:145) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597)
RUNNABLE(正在运行的,消耗cpu) TIMED_WAITING(等待被分配到cpu运行的,现在不消耗cpu) BLOCKED(被阻塞,在阻塞解除前不能被分配cpu执行,现在不消耗cpu)
exec-613占用了锁,exec-553需要的锁被exec-613占用无法执行处于blocked状态.
NioProcessor-1正在运行,并且他的方法在调用native方法.
RMI TCP Connection(8)-172.25.3.81正在运行.
(一) jinfo jinfo打印一个给定的Java进程或核心文件或一个远程调试服务器的Java配置信息。配置信息包括Java系统属性和JVM命令行标志(更多信息,请参考《jinfo-Configuration Info》)。 (二) jmap jmap:如果这个工具不使用任何选项(除了pid或core选项)运行,那么它显示类似于Solaris的pmap工具所输出的信息。这个工具支持针对Java堆可观察性的若干其它选项。 在Java SE 6平台中,新加入了一个-dump选项。这样可以使jmap能够把Java堆信息复制到一个文件中,然后我们可以使用新的jhat命令(见下面一节)来分析它。 jmap -dump选项并不使用Solaris libproc来实现实时处理;而是,它运行当前正运行的JVM中的一小段代码,由此来实现堆复制。既然这种堆复制代码运行于JVM内部,那么其速度是比较快的。堆复制的效果大致相当于实现一次"完全的GC"(对整个堆的垃圾收集),再加上把该堆的内容写入到文件中。实现堆复制的另外一种可能的思路是使用 gcore来进行核心复制并且运行"jmap -dump"(这与以"离线"方式运行的核心复制形成对照)。
可以输出某个java进程内存内对象的情况,甚至可以将VM 中的heap,以二进制输出成文本。
|
|
(三) jstack jstack等价于Solaris的pstack工具。jstack打印所有的Java线程的堆栈跟踪信息(可选地包括本机帧信息),请参考《jstack-堆栈跟踪》。关于锁和死锁的信息也可以被打印,请参考java.util.concurrent locks。 top 时 H显示线程情况 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAN
D 29390 mqq 15 0 1696m 1.3g 8784 S 100 16.3 1:46.19 java 29889 mqq 16 0 1696m 1.3g 8784 S 6 16.3 1:23.26 java 29904 mqq 15 0 1696m 1.3g 8784 S 2 16.3 0:14.87 java 29849 mqq 15 0 1696m 1.3g 8784 S 1 16.3 0:14.68 java 29388 mqq 16 0 1696m 1.3g 8784 S 1 16.3 0:12.55 java 29850 mqq 15 0 1696m 1.3g 8784 S 1 16.3 0:15.26 java 其中线程29390转换为16进制为0x72ce,可以查出对应比较耗时线程在做什么 jstack 29364|grep -A10 72ce "Thread-2" prio=10 tid=0x00002aab0b603c00 nid=0x72ce runnable [0x000000004133b000..0x000000004133baa0] java.lang.Thread.State: RUNNABLE at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:215) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65) at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69) - locked <0x00002aaab2d972e8> (a sun.nio.ch.Util$1) - locked <0x00002aaab2d972d0> (a java.util.Collections$UnmodifiableSet) - locked <0x00002aaab2283470> (a sun.nio.ch.EPollSelectorImpl) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:84)
(四) jsadebugd jsadebugd依附到一个Java进程或核心文件并且担当一个调试服务器的作用。远程客户,例如jstack、jmap和jinfo,都能够通过Java RMI依附到该服务器。 (五) jhat jhat是一个Java堆复制浏览器。这个工具分析Java堆复制文件(例如,由上面的 "jmap -dump"所产生的)。Jhat启动一个允许堆中的对象在web浏览器中进行分析的web服务器。这个工具并不是想用于应用系统中而是用于"离线"分析。"jhat工具是平HIDDEN立的",其意思是,它可以被用来观察在任何平台上所产生的堆复制。例如,我们有可能在Linux系统上使用jhat来观察一个在Solaris OS上所产生的堆复制。
(六)jstat 查出gc情况
很强大的监视jvm内存工具,可用来查看堆内各个部分的使用量,以及加载类的数量。使用时,需指定java进程号。 一般使用 -gcutil 查看gc情况。
|
jstat -compiler pid:显示VM实时编译的数量等信息。
jstat -gc pid:可以显示gc的信息,查看gc的次数及时间。其中最后五项,分别是young gc的次数,young gc的时间 ,full gc的次数,full gc的时间,gc的总时间。 jstat -gccapacity pid:可以显示VM内存中三代(young,old,perm)对象的使用和占用大小,如:PGCMN显示的是 最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是当 前perm内存占用量。
语法结构如下:jstat [Options] vmid [interval] [count] Options — 选项,我们一般使用 -gcutil 查看gc情况 vmid — VM的进程号,即当前运行的java进程号 interval– 间隔时间,单位为秒或者毫秒 count — 打印次数,如果缺省则打印无数次 S0 — Heap上的 Survivor space 0 区已使用空间的百分比 S1 — Heap上的 Survivor space 1 区已使用空间的百分比 E — Heap上的 Eden space 区已使用空间的百分比 O — Heap上的 Old space 区已使用空间的百分比 P — Perm space 区已使用空间的百分比 YGC — 从应用程序启动到采样时发生 Young GC 的次数 YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒) FGC — 从应用程序启动到采样时发生 Full GC 的次数 FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒) GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
实例使用1:
[root@localhost bin]# jstat -gcutil 25444
S0 S1 E O P YGC YGCT FGC FGCT GCT
11.63 0.00 56.46 66.92 98.49 162 0.248 6 0.331 0.579
实例使用2:
[root@localhost bin]# jstat -gcutil 25444 1000 5
S0 S1 E O P YGC YGCT FGC FGCT GCT
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
jps
与ps命令类似,用来显示本地的java 进程,查看本地运行着几个java应用,并显示进程号。
|
|
sar : 既能收集系统 CPU 、硬盘网络设备等动态数据,更能查看二进制数据文件等。
用法:
sar [参数选项] t [n] [-o file] ( t 为采样间隔秒,必须有, n 为采样次数,可选,默认值 1 )
参数说明:
-A 显示所有历史数据,通过读取/var/log/sar 目录下的所有文件,并把它们分门别类的显示出来; -b 通过设备的I/O 中断读取设置的吞吐率; -B 报告内存或虚拟内存交换统计; -c 报告每秒创建的进程数; -d 报告物理块设备(存储设备)的写入、读取之类的信息,如果直观一点,可以和p 参数共同使用,-dp -f 从一个二进制的数据文件中读取内容,比如sar -f filename ; -n 分析网络设备状态的统计,后面可以接的参数有DEV 、EDEV 、NFS 、NFSD 、SOCK 等。比如-n DEV -o 把统计信息以二进制格式写入一个文件,比如-o filename ; -u 报告CPU 利用率的参数; -P 报告每个处理器应用统计,用于多处理器机器,并且启用SMP 内核才有效; -p 显示友好设备名字,以方便查看,也可以和-d 和-n 参数结合使用;
-r 内存和交换区占用统计; -t 这个选项对从文件读取数据有用,如果没有这个参数,会以本地时间为标准读出; -v 报告inode, 文件或其它 内核表的资源占用信息; -w 报告系统交换活动的信息; 每少交换数据的个数; -W 报告系统交换活动吞吐信息; -x 用于监视进程的,在其后要指定进程的PID 值; -X 用于监视进程的,但指定的应该是一个子进程ID ;
CPU利用率:
|
%usr cpu 用户模式下时间(百分比) %sys cpu 系统模式下时间(百分比)
%nice 表示 CPU 在用户层优先级的百分比, 0 表示正常; %iowait cpu 等待输入 / 输出完成(时间百分比) %idle cpu 空闲时间(百分比)
将动态信息写入文件中:
[root@localhost ~]#sar -u 1 5 > sar000.txt [root@localhost ~]# cat sar000.txt
也可以输出到一个二进制的文件中,然后通过 sar 来查看;
[root@localhost ~]#sar -u 1 5 -o sar002 [root@localhost ~]# sar -f sar002
网络设备的吞吐情况:
|
IFACE:设备名; rxpck/s:每秒收到的包; rxbyt/s:每秒收到的所有包的体积 txbyt/s:每秒传输的所有包的体积; rxcmp/s:每秒收到数据切割压缩的包总数; txcmp/s:每秒传输的数据切割压缩的包的总数; rxmcst/s:每秒收到的多点传送的包。
iostat:用法:
iostat [ -c | -d ] [ -k ] [ -t ] [ -V ] [ -x [ device ] ] [ interval [ count ] ]> outputfile
其中, -c为汇报CPU的使用情况; -d为汇报磁盘的使用情况; -k表示每秒按kilobytes字节显示数据; -x可获得更多信息; interval指每次统计间隔的时间; count指按照这个时间间隔统计的次数。
[root@B1943 ~]# iostat -d -k -x Linux 2.6.18-53.el5 (B1943) 2011年01月13日 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sda 0.02 4.10 0.11 1.77 2.11 23.45 27.24 0.00 1.15 0.63 0.12 rrqm/s:每秒这个设备相关的读取请求有多少被Merge了(当系统调用需要读取数据的时候,VFS将请求发到各个FS,如果FS发现不同的读取请求读取的是相同Block的数据,FS会将这个请求合并Merge);
wrqm/s:每秒这个设备相关的写入请求有多少被Merge了。
rsec/s:每秒读取的扇区数;
wsec/s:每秒写入的扇区数。
r/s:The number of read requests that were issued to the device per second;
w/s:The number of write requests that were issued to the device per second;
await:每一个IO请求的处理的平均时间(单位是微秒)。这里可以理解为IO的响应时间,一般地系统IO响应时间应该低于5ms,如果大于10ms就比较大了。
%util:在统计时间内所有处理IO时间,除以总共统计时间。例如,如果统计间隔1秒,该设备有0.8秒在处理IO,而0.2秒闲置,那么该设备的%util = 0.8/1 = 80%,所以该参数暗示了设备的繁忙程度。一般地,如果该参数是100% 表示设备已经接近满负荷运行了(当然如果是多磁盘,即使%util是100%,因为磁盘的并发能力,所以磁盘使用未必就到了瓶颈)。
[root@B1943 ~]# iostat -d -k Linux 2.6.18-53.el5 (B1943) 2011年01月13日
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn sda 1.88 2.11 23.45 1122861 12458568
tps:该设备每秒的传输次数(Indicate the number of transfers per second that were issued to the device.)。“
一次传输”意思是“一次I/O请求”。多个逻辑请求可能会被合并为“一次I/O请求”。“一次传输”请求的大小是未知的。 kB_read/s:每秒从设备(drive expressed)读取的数据量;
kB_read:读取的总数据量;
kB_wrtn/s:每秒向设备(drive expressed)写入的数据量;
kB_wrtn:写入的总数量数据量;这些单位都为Kilobytes。
iostat -cdx 1 >outfile
cat outfile
vmstat: 也可以输出到文件vmstat > outputfile
Procs r: The number of processes waiting for run time. b: The number of processes in uninterruptable sleep. w: The number of processes swapped out but otherwise runnable.
Memory swpd: the amount of virtual memory used (kB). free: the amount of idle memory (kB). buff: the amount of memory used as buffers (kB).
Swap si: Amount of memory swapped in from disk (kB/s). so: Amount of memory swapped to disk (kB/s).
IO bi: Blocks sent to a block device (blocks/s). bo: Blocks received from a block device (blocks/s).
System in: The number of interrupts per second, including the clock. cs: The number of context switches per second.
CPU These are percentages of total CPU time. us: user time sy: system time id: idle time
文件句柄数Too many open files
问题描述:java .io.IOException: Too many open files
|
ulimit应该是用户的限制 ,如果太小则修改大小:ulimit -n 2048
如果要重启后仍生效,则可修改/etc/security/limits.conf,后面加上: * - nofile 2048 (此时type用“-”,表示hard和soft同时设定。domain设置为星号代表全局,也可以针对不同的用户做出不同的限制 )
/proc/sys/fs/file-max应该是系统级的限制
|
如果要重启后仍生效,则可修改 /etc/sysctl.conf,加上:fs.file-max = 65536
另外还有一个,/proc/sys/fs/file-nr
只读,可以看到整个系统目前使用的文件句柄数量