linux java程序cpu占用高跟踪
- 问题描述:
- 解决过程:
- 1. top命令检查CPU占用较高进程
- 2. 命令 ps -mp 【替换为进程ID PID】 -o THREAD,tid,time,查看该进程占用高的线程;
- 3. 命令printf "%x\n" pid,把tid转换为16进制;
- 4. 命令jstack pid| grep tid(十六进制) -A 10 -B 30,查看进程pid为17084且tid为5b0f的线程的栈信息 前后30行;
- 5.命令jmap -histo,查看进程里占用的对象情况,我这里截取占用内存最多的前30条日志;
- 6. 垃圾回收问题,使用jstat -gcuti pid命令,查看进程Java内存分布及回收情况来验证,间隔1秒打印一次,共5次;
- 7. 垃圾回收问题,通过jmap -heap pid命令,打印栈情况来验证信息
- 8.垃圾回收问题,通过jstack命令,输出dump文件来验证
问题描述:
压测过程中,发现tomcat服务器不灵活,刷新(控制台)页面长时间转圈,无响应。需要找出CUP占用较高原因,进行分析并且优化
解决过程:
1. top命令检查CPU占用较高进程
top
发现pid为17084的java进程占用服务器cpu很高
2. 命令 ps -mp 【替换为进程ID PID】 -o THREAD,tid,time,查看该进程占用高的线程;
ps -mp 17084 -o THREAD,tid,time | sort -rn
发现tid为23311的线程占用CPU较高且吃香时间较长,图2效果更明显,是调优前效果,作为对比图用
图1
图2
3. 命令printf “%x\n” pid,把tid转换为16进制;
printf “%x\n” 23311
输出结果为5b0f
4. 命令jstack pid| grep tid(十六进制) -A 10 -B 30,查看进程pid为17084且tid为5b0f的线程的栈信息 前后30行;
jstack 17084 | grep 5b0f -A 10 -B 30
从报错信息中可以看到为,栈异常导致频繁GC,想要继续追踪其他参数,进一步确认问题
图2为效果更明显图
5.命令jmap -histo,查看进程里占用的对象情况,我这里截取占用内存最多的前30条日志;
jmap -histo 17084 | sort -n -r -k 2 | head -30
由图基本上问题已经定位到了,走查代码发现:主要应该是频繁分发那里的代码逻辑,创建了太多得对象,导致大量引用一直存活,且越来越多(GCRoot可达),从6中图2可以看到,青年代和老年代占满,频繁full jc STW,压测请求堆积压垮服务器。
也可以通过
6. 垃圾回收问题,使用jstat -gcuti pid命令,查看进程Java内存分布及回收情况来验证,间隔1秒打印一次,共5次;
jstat -gcutil统计gc信息显示列名具体描述
jstat -gcutil 17084 1s 5
从图中相同代码,不同时间段产生的YGC和FGC的次数可以看出GC频繁,且FGC频繁(pid为33745的进程效果比较明显,作为对比图使用)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
M:metadate已使用的占当前容量百分比
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
-
6.1
从jstat -gcutil信息来看,可以看到GC是ParallelScavenge(-XX:+UseParallelGC,在server class machine上这个是默认GC;以及有可能full GC用的是PSCompact)
-
6.2
ParallelScavenge有个默认打开的功能叫做adaptive size policy,在应用分配内存压力很大的时候有可能会把两个survivor space都收缩到最小(几乎为0),于是young gen基本上就整个都是eden,经历young GC能存活下来的对象就会直接晋升到old gen去,等到full GC的时候再看能不能回收。从 jstat -gcutil 的空间占用数据似乎是反映了这种情况 -
6.3
而ParallelScavenge在触发GC的时候,要么会触发单独的young GC,要么会触发young GC + full GC(这里是full GC前会关联触发一次young GC,这个行为由 -XX:+ScavengeBeforeFullGC 参数控制)。在触发full GC的时候,它有可能一口气循环接连触发多次full GC——如果跑了一次full GC还没回收到足够内存,那么会再“用力”一点再触发一次full GC,不行就再“用力”一点…(“用力”例如说ParallelScavenge的默认策略一开始是不会强迫清理SoftReference的,但需要再“用力”一点的时候就会清理SoftReference。) -
进程号pid为33745 的 jstat -gcutil 的GC次数数据似乎是反映了这种情况
-
6.4 jstat -gc pid【进程号】也可以查看gc的信息,查看gc的次数,及时间
jstat -gc 17084
S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC:年轻代中Eden(伊甸园)的容量 (字节)
EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC:Old代的容量 (字节)
OU:Old代目前已使用空间 (字节)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
7. 垃圾回收问题,通过jmap -heap pid命令,打印栈情况来验证信息
jmap -heap 37745
从图中可以明显看到Old Generation使用率为93.17234102692849%
图2为效果显著图
Heap Configuration: #堆配置情况
-
MinHeapFreeRatio = 40 #堆最小使用比例
-
MaxHeapFreeRatio = 70 #堆最大使用比例
-
MaxHeapSize = 8589934592 (8192.0MB) #堆最大空间
-
NewSize = 1363144 (1.2999954223632812MB) #新生代初始化大小
-
MaxNewSize = 5152702464 (4914.0MB) #新生代可使用最大容量大小
-
OldSize = 5452592 (5.1999969482421875MB) #老生代大小
-
NewRatio = 2 #新生代比例
-
SurvivorRatio = 8 #新生代与suvivor的占比
-
MetaspaceSize = 21807104 (20.796875MB) #元数据空间初始大小
-
CompressedClassSpaceSize = 1073741824 (1024.0MB) #类指针压缩空间大小, 默认为1G
-
MaxMetaspaceSize = 17592186044415 MB #元数据空间的最大值, 超过此值就会触发 GC溢出( JVM会动态地改变此值)
-
G1HeapRegionSize = 2097152 (2.0MB) #区块的大小
Heap Usage: G1 Heap:
- regions = 4096 # G1区块初始化大小
- capacity = 8589934592 (8192.0MB) #G1区块最大可使用大小
- used = 1557972768 (1485.7986145019531MB) #G1区块已使用内存
- free = 7031961824 (6706.201385498047MB) #G1区块空闲内存
- 18.137190118432045% used #G1区块使用比例G1
Young Generation: 新生代
Eden Space: #Eden区空间 - regions = 670
- capacity = 2699034624 (2574.0MB)
- used = 1405091840 (1340.0MB)
- free = 1293942784 (1234.0MB)
- 52.05905205905206% used
Survivor Space: #Survivor区 - regions = 3
- capacity = 6291456 (6.0MB)
- used = 6291456 (6.0MB)
- free = 0 (0.0MB)
- 100.0% used
G1 Old Generation: #老生代 - regions = 72
- capacity = 1589641216 (1516.0MB)
- used = 146589472 (139.79861450195312MB)
- free = 1443051744 (1376.2013854980469MB)
- 9.221544492213267% used
8.垃圾回收问题,通过jstack命令,输出dump文件来验证
jstack -l 17804 > jstack.log
图1
图2
图3
图4
-
问题分析
通过图1,图2,图3可以看出:
搜索java.lang.Thread.State: RUNNABLE ,共520次,说明所有线程都处于运行状态
搜索java.net.SocketInputStream.socketRead0(Native Method) ,共500次,说明tomcat线程都阻塞在socket的read方法。
搜索at com.telecomyt.mdm.sms.api.AuthApi.getMember(AuthApi.java:98),共500次,此为业务代码
根据日志查看业务代码:
String response = HttpUtil.postJson( authApi.ucenterProperties.getUrl() + authApi.ucenterProperties.getMemberInfoUri(),GsonUtil.toJson(memberDto),null)
使用的java.net的http请求,并且没有配置超时时间,默认超时时间为-1,导致服务调用超时却未关闭,大量占用线程,随着压测数据增加造成大量引用无法回收。
参考文档1
参考文档2