问题定位 作者 : houjt 最后修改日期 :20180619 注意:所有涉及公司相关的信息(例如IP,系统名称等),在文件发布时,必须抹掉。 目录 问题定位 作者 : houjt 最后修改日期 :20180619 1 CPU过高(2) 1.1 定位CPU过高的进程/线程 1.2 堆栈日志的排查 1.3 对代码进行走读和排查 1.4 常见cpu现象和解决方法 a、代码中写死循环时,一直占用cpu b、在循环中不停的创建对象放入List,处理完业务后没有清空List,也会导致GC频繁 2 内存溢出=OOM(4) 2.1 原因与定位 2.2 解决方案 3 程序挂死(6) 3.1 原因与定位 3.2 解决方案 4 GC频繁发生(1) 4.1 原因与定位 4.2 解决方案: 5 附录 5.1 博文链接 样式: 代码区底色 1 CPU过高(2)
1.1
定位CPU过高的进程/线程
先用top命令,找到cpu占用最高的进程,记住PID; 再用ps -mp pid -o THREAD,tid,time 查询进程中cpu占用率高的线程,记住TID; 接着通过jstack pid
>> xxx.log ,将进程下的线程堆栈日志dump出来;(dump也就是打印意思) sz xxx.log 将日志文件下载到本地 将上面查找到的线程占用最高的 tid 转成16进制 打开下载好的 xxx.log 通过 查找方式 找到 对应线程 进行排查 1.2
堆栈日志的排查
线程的CPU过高是表象,线程所处的状态是原因。分析底层为什么处于该种状态,就可以定位和解决。 1.3
对代码进行走读和排查
用过哪些堆栈分析工具 首先要清楚线程的状态 线程的状态有:new、runnable、running、waiting、timed_waiting、blocked、dead 线程状态变迁图: 各状态说明: New: 当线程对象创建时存在的状态,此时线程不可能执行; Runnable:当调用thread.start()后,线程变成为Runnable状态。只要得到CPU,就可以执行; Running:线程正在执行; Waiting:执行thread.join()或在锁对象调用obj.wait()等情况就会进该状态,表明线程正处于等待某个资源或条件发生来唤醒自己; Timed_Waiting:执行Thread.sleep(long)、thread.join(long)或obj.wait(long)等就会进该状态, 与Waiting的区别在于Timed_Waiting的等待有时间限制; Blocked:如果进入同步方法或同步代码块,没有获取到锁,则会进入该状态; Dead:线程执行完毕,或者抛出了未捕获的异常之后,会进入dead状态,表示该线程结束 其次,对于jstack日志,我们要着重关注如下关键信息 Deadlock:表示有死锁 Waiting on condition:等待某个资源或条件发生来唤醒自己。具体需要结合jstacktrace来分析,比如线程正在sleep,网络读写繁忙而等待 Blocked:阻塞 Waiting on monitor entry:在等待获取锁 in Object.wait():获取锁后又执行obj.wait()放弃锁 对于Waiting on
monitor entry 和 in
Object.wait()的详细描述:Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。从下图中可以看出,每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 "Active
Thread",而其它线程都是 "Waiting Thread",分别在两个队列 " Entry Set"和 "Wait
Set"里面等候。在 "Entry Set"中等待的线程状态是 "Waiting for monitor entry",而在 "Wait Set"中等待的线程状态是 "in
Object.wait()" 举例: prio:线程的优先级 tid:线程id nid:操作系统映射的线程id, 非常关键,后面再使用jstack时补充; 1103e9000 与 106692000 :表示线程栈的起始地址。 上面截图,可以查看到堆栈中正在执行的代码! 1.4
常见cpu现象和解决方法
a、代码中写死循环时,一直占用cpu
解决:如果加上线程睡眠时间,则释放cpu占用,不会一直抢占cpu b、在循环中不停的创建对象放入List,处理完业务后没有清空List,也会导致GC频繁
解决:循环中的不断创建对象,保存在List;List参与后续的业务处理。业务处理完毕后,记得清空当前循环下的List。 死循环和GC频繁都会导致CPU过高。 2
内存溢出=OOM(4)
2.1
原因与定位
内存溢出的场景很多,但是从引起内存溢出的原因来看,常见的也就是几种: 了解omm的成因,也就可以逐一排查与定位到问题所在:
1、检查服务启动时预设的内存值是否过小,或者提交应用时的预分配内存资源是否过小。 2、检查内存中加载的数据量是否过于庞大,如一次从数据库取出过多数据。 3、检查代码中是否存在死循环,或者循环过程中产生过多重复的对象实例。 4、检查集合中对象的引用,在其使用完后是否未清空,从而导致JVM不能回收。 2.2
解决方案
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。) 第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。 第三步,对代码进行走读和排查,找出可能发生内存溢出的位置。——排查的内容也就是上面那些omm的成因 重点排查以下几点: 1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内 存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查 询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。 2.检查代码中是否有死循环或递归调用。 3.检查是否有大循环重复产生新对象实体。 4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内 存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查 询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。 5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使 得这些对象不能被GC回收。 第四步,使用内存查看工具动态查看内存使用情况。 —— 个人比较少用,一直用的都是前三步 3
程序挂死(6)
3.1
原因与定位
程序挂死的场景很多,但是从引起程序挂死的原因来看,常见的也就是几种: 了解程序挂死的成因,也就可以逐一排查与定位到问题所在: 1、程序中有死循环; 2、程序实际运行的时间比所期望的长; 3、程序运行所需的资源不足(内存/Core),一直在等待足够的空闲资源。 5、程序连接数据库时,网络不稳定,或者没有设置超时机制。或者有2个线程同时读写同一条记录,触发数据库的自我保护锁死。 6、程序本身设计的目的就是为了延迟一段时间,或者暂停执行。 3.2
解决方案
第一步,打印应用的工作日志,查看当前代码执行的位置,从而初步了解当前代码执行的功能。 第二步,通过jstack,将应用的线程堆栈日志dump出来。 排查处于挂起状态的线程,通过目标线程的堆栈信息可以看到正在执行的代码。 通过代码走读,排查分析程序挂死的真正原因。 4
GC频繁发生(1)
4.1
原因与定位
(1)人为原因
例如在代码中调用System#GC或者Runtime#GC方法。 (2)框架原因
在java程序调用相关框架时,框架内部调用了GC方法。 (3)内存原因
当heap大小设置比较小时,会引起频繁的GC,所以在类似于Spark这样对内存性能要求比较高的应用程序运行时,应可能给heap分配较大的内存,这样可以减少频繁的GC现象的发生。 内存不足,需要分配更大的内存时就触发GC过程。 排除人为原因或者框架自己调用GC的原因,主要就是内存不足,导致GC发生。 一方面,程序频繁创建jvm对象(用完没有主动释放),另一方面每次GC释放的资源太少, Jvm对象存活率过高,在新生区不断引发minor GC, 老年区不断引发Full GC。 当full GC完成后,仍然没有足够的内存,则会抛出OOM 从内存结构来分析, Minor GC(小型GC),发生在新生区的GC,具体是survivor的GC。 Full GC
就是老年区发生的GC。对全量数据进行GC。
一般的,Minor GC的发生频率要比Full GC高很多。 一般所说的频繁GC指的也就是频繁的Full GC,大批量的内存分配申请,处理不过来。 l Minor GC ,Full GC 触发条件 Minor GC触发条件:当Eden区满时,触发Minor GC。 Full GC触发条件: (1)调用System.gc时
(2)老年区空间不足 (3)方法区空间不足 (4)空间分配担保,每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小 当我们调用System.gc()的时候,其实并不会马上进行垃圾回收,甚至不一定会执行垃圾回收 当我们需要调用的System.gc()的时候 要这样才会执行: System.gc(); runtime.runFinalizationSync(); System.gc(); 不过,个人建议不到万不得已不要调用,因为jvm有自己的gc策略,根本不需要我们来手动 GC频繁,说明程序还是可以运行, 但是当执行Full GC后空间仍然不足,则会抛出OOM 4.2
解决方案:
1)在启动参数中,指定垃圾收集器并设置打印GC日志。 通过 -XX:+UseSerialGC 选项,指定JVM使用串行垃圾收集器, 并使用下面的启动参数让 JVM 打印出详细的GC日志:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps 2)分析GC日志 http:///detail/56709-gc-%E6%97%A5%E5%BF%97
2015-05-26T14:45:37.987-0200 1: 151.126 2:[ GC 3( Allocation Failure 4) 1. 2015-05-26T14:45:37.987-0200 – GC事件(GC event)开始的时间点. 2. 151.126 – GC时间的开始时间,相对于JVM的启动时间,单位是秒(Measured in
seconds). 3. GC – 用来区分是 Minor GC 还是
Full GC 的标志(Flag). 这里的 GC 表明本次发生的是 Minor GC. 4. Allocation Failure – 引起垃圾回收的原因. 本次GC是因为年轻代中没有任何合适的区域能够存放需要分配的数据结构而触发的. 5. DefNew – 使用的垃圾收集器的名字. DefNew 这个名字代表的是: 单线程(single-threaded),
采用标记复制(mark-copy)算法的, 使整个JVM暂停运行(stop-the-world)的年轻代(Young generation) 垃圾收集器(garbage
collector). 6. 629119K->69888K – 在本次垃圾收集之前和之后的年轻代内存使用情况(Usage). 7. (629120K) – 年轻代的总的大小(Total size). 8. 1619346K->1273247K – 在本次垃圾收集之前和之后整个堆内存的使用情况(Total used heap). 9. (2027264K) – 总的可用的堆内存(Total available
heap). 10. 0.0585007 secs – GC事件的持续时间(Duration),单位是秒. 11. [Times: user=0.06 sys=0.00, real=0.06 secs] – GC事件的持续时间,通过多种分类来进行衡量: · user – 此次垃圾回收, 垃圾收集线程消耗的所有CPU时间(Total CPU time). · sys – 操作系统调用(OS call) 以及等待系统事件的时间(waiting for
system event) · real – 应用程序暂停的时间(Clock time). 由于串行垃圾收集器(Serial Garbage
Collector)只会使用单个线程, 所以 real time 等于 user 以及 system time 的总和. GC的类型:minor GC GC的原因:Allocation
Failure GC前后的内存使用情况: GC持续的时间。 3)也可以直接使用GC工具去查看。 http:///detail/52895-gc-%E6%97%A5%E5%BF%97-%E5%88%86%E6%9E%90
(1)GCHisto http:///projects/gchisto (2)GCLogViewer http://code.google.com/p/gclogviewer/ 4)代码走读,排查。 5
附录
5.1
博文链接
https://blog.csdn.net/coderpopo/article/details/80332496 如何排查CPU占用过高以及常见的几种情况 https://blog.csdn.net/zxh87/article/details/52137335 jstack日志深入理解 https://www.cnblogs.com/poles/p/6039987.html Thread.join简单介绍 |
|