现象
今天, 生产上的springboot 应用cpu 达到200%, 即占用了2核, 线上应用奔溃, 应用无法访问。在立刻重启应用后,应用恢复正常, 奇怪的是, 在一段时间后, 服务又出现无法访问的情况。
问题分析
该问题可以大致上看成2类, 可能也可能没有直接关联
- 服务宕机
- 应用占用CPU很高
排查思路及手段
因为不同的问题排查思路及方式会不同, 针对本次服务宕机的排查问题追溯如下:
通过jps查看应用是否运行 > 通过Top查看系统运行状况 > 查看错误日志 >
排查细节:
1、 使用jps查看是哪个进程编号pid 对应哪个应用
jps: Java Virtual Machine Process Status Tool
[root@localhost ~]# jps
26610 Bootstrap
28877 jar
18039 TSDMain
26091 Kafka
19456 Jps
如果发现应用不在列表中, 则本次服务无法访问则是因为应用未启动造成, 则需要排查是什么原因造成服务未启动。
可能原因:
- linux oom killer 将应用杀死
- 人为杀死(可能性很小)
- 操作系统宕机等原因(如果操作系统在云服务上,可能性极小)
此时, 我发现应用还在列表中。
2. 查看错误日志
java 应用在线上会以文件的形式记录warn级别及以上的错误日志, 发现有大量以下内容的错误日志:
"Cannot serialize; nested exception is
org.springframework.core.serializer.support.SerializationFailedException: Failed to
serialize object using DefaultSerializer; nested exception is java.lang.OutOfMemoryError:
PermGen space"
以上错误很明显了: 内存溢出, 永久代内存空间不足。
2.1查看应用永久代内存空间大小
> jstat -gcpermcapacity pid #perm对象的信息及其占用量
PGCMN PGCMX PGC PC YGC FGC FGCT GCT
21504.0 83968.0 83968.0 83968.0 361 2 0.906 39.789
# 同时我们可以通过jstat -gcutil来动态查看gc的状态
> jstat -gcutil pid
大致情况如下:
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 11.13 11.97 40.61 33.67 5508 44.783 22 10.686 55.469
(以上是jvm参数加入后的状态, 之前看到P是99%以上)
经过换算, 发现perm代的最大容量才81M。
本次解决办法:
设置jvm参数, 增大永久代内存的大小
(java -XX:MaxPermSize=512M -jar xxxx.jar &)
之后服务不再出现无法提供服务的问题。
事后回顾及思考:
应用通过jenkins发布, 构建后执行脚本运行springboot的应用, 采用的是java -jar xxx.jar , 故采用默认配置, 从而忽略了生产对于内存要求的苛刻性。
所以对于生产环境的配置, 往往要结合并分析实际业务场景, 选择适合的配置, 当然有时候不能盲目加入硬件配置, 也可以通过一定的逻辑优化来解决问题。
知识补充:(针对该现象)
- 内存中什么对象是永久代
有时候把永生代和方法区对等, 方法区是多个线程共享的区域, 存储常量信息,类信息,方法信息。
- JVM性能调优监控工具 使用
往往大多数程序员忽视了这方面的知识, 对关于jvm的问题束手无策, 其实jdk中为我们提供了大量的jvm调优及监控工具, 比如jps、jstack、jmap、jhat、jstat。
在本次我们使用了jps和jstat来查看问题, 当然这些命令还有其他参数,如果感兴趣的话可以在网上搜索。
针对CPU占用高的问题
系统CPU占用高曾经也出现过,所以很快能够排查出大致问题所在。
大致思路
1. 检查是哪个进程占用了高cpu
> top # top命令经常用来监控linux的系统状况
top命令示意图
有时候会看到mysql进程占用很高cpu
一般mysql的配置dba会配置好,往往不是mysql配置的问题, 所以 对于开发人员来说, 首先看慢sql日志。如果此时发现有大量慢sql日志产生, 则需要结合实际业务场景进行相应的sql及逻辑优化, 有必要的话可以通过一些缓存手段来解决问题。
如果是java 应用占用很高cpu
2 java应用CPU占用高
这时候我们开始对具体的应用进行分析了, 我们可以借用一些工具来分析java进程。如
jstack、jmap、jhat、hprof。
我的做法是首先定位占用高cpu的线程:
步骤如下:
- 通过jps获取进程id
- 通过jstack 查找线程dump
> ps -mp pid -o THREAD,tid,time
线程
将线程id转换为16进制(以方便找到dump中对应的线程)
> printf "%x\n" tid
> jstack pid |grep tid -A 60 这里的tid为转换的16进制, grep -A n 为匹配的行数后n条
查询结果大致如下:
"container-0" prio=10 tid=0x00007f9a14e1e800 nid=0x465a waiting on condition [0x00007f9a6dc57000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:427)
at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer$1.run(TomcatEmbeddedServletContainer.java:177)
"VM Thread" prio=10 tid=0x00007f9a7812f800 nid=0x45ec runnable
"GC task thread#0 (ParallelGC)" prio=10 tid=0x00007f9a7801e000 nid=0x45dd runnable
如果定位发现cpu高的线程为GC, 很有可能是GC出现了问题。
如果线程dump中执行的是某业务代码, 可能是发生了死循环之类的。
参考:
[1] jstat 命令详解
[2] Java SE Specifications Chapter 2. The Structure of the Java Virtual Machine
[3] 《深入理解java虚拟机 JVM高级特性与最佳实践》
网友评论