# jvmtest **Repository Path**: ganyigan/jvmtest ## Basic Information - **Project Name**: jvmtest - **Description**: jvm故障排查方案 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-12 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JVM故障排查

## cpu高问题 ### 模拟程序 IteratorCpuMax类 启动脚本 ```sh nohup java com.rk.jvm.IteratorCpuMax > IteratorCpuMax.out & ``` ### 现象 cpu使用率高(100%),内存使用率正常,可以使用top发现异常。 ### 排查思路 1. 确定故障进程程 使用`top`命令观察进程情况,输入P根据cpu使用率排序,找出对应进程。 ![](./image/top.png) pid=1537 2. 确定故障线程 使用`top -H -p pid`查看进程内的线程情况。 ![](./image/top2.jpg) tid=1538 也可以`ps -mp pid -o THREAD,tid,time`查看更多的信息。 ![](./image/ps -mp.png) tid=1538的线程,cpi使用率97.6%,线程执行时间23分钟08秒。 3. 查看线程执行情况 jstack中的tid为16进制,此处需要进制转换,可使用`printf %x tid`,`printf %x 1538 ==》 602`。 通过jstack查看线程执行情况`jstack pid | grep tid -A 50`。 ![](./image/jstack.png) ps:linux中的tid对应的是jstack中的nid,jstack中的tid为java内的线程id。 如果java栈较复杂的情况可以通过`jstack pid > stack.txt`生成全量内容,进行分析。 4. 确认代码问题 ![](./image/deadIterator.png) 原因为for循环中dataMap没有数据,外层iterator.hasNext()始终为true,导致死循环。 #### 线程状态介绍 - 常见线程状态 RUNNABLE:当调用thread.start()后,线程变成为Runnable状态。只要得到CPU,就可以执行 BLOCKED:如果进入同步方法或同步代码块,没有获取到锁,则会进入该状态(进入synchronized之前) WAITING:执行thread.join()或在锁对象调用obj.wait()等情况就会进该状态,表明线程正处于等待某个资源或条件发生来唤醒自己(已经进入synchronized,调用了wait()) TIMED_WAITING:执行Thread.sleep(long)、thread.join(long)或obj.wait(long)等就会进该状态,与Waiting的区别在于Timed_Waiting的等待有时间限制 Running:线程正在执行,目前持有CPU资源 - 锁状态说明 当一个线程占有一个锁的时候,线程堆栈会打印一个-locked<0x22bffb60> 当一个线程正在等在其他线程释放该锁,线程堆栈会打印一个-waiting to lock<0x22bffb60> 当一个线程占有一个锁,但又执行在该锁的wait上,线程堆栈中首先打印locked,然后打印-waiting on <0x22c03c60> ### 常用套路 - jstack存在量的线程waiting to lock 某个地址,只要排查锁对象和持有锁的线程就可以定位问题原因 - cpu占满基本都是死循环造成的,问题线程状态一般为RUNNABLE - 请求处理慢,但资源(cpu、内容、各种IO)使用都较低,很大可能是大量线程处于BLOCKED和WAITING状态

## 内存泄漏 ### 模拟程序 FullGC类 启动脚本 ```sh nohup java -Xms50M -Xmx50M -XX:+PrintGC com.rk.jvm.FullGC > FullGC.out & ``` ### 现象 cpu使用率高,内存使用占满(虚拟机资源为1c1g),可以使用top发现异常,gc日志全部为Full GC,有时候也可能出现out of memory。 ### 排查思路 1. 首先排查java栈运行情况,过程同上(生产环境堆内存分配较大,不容易分析,实在没办法时候才考虑dump分析)。 2. 确定故障进程程 使用`top`命令观察进程情况,找出对应进程。 ![](./image/top3.png) `top`看到的内存包含了堆外内存,会比实际分配内存大,具体可查看gc日志。 ![](./image/gc.png) 3. 确定故障线程`top -H -p 3327 ` ![](./image/top4.png) 看到问题线程为VM Thread,说明jvm本身占用了大量的cpu资源,也可以定位为GC问题。 4. 查看线程执行情况`jstack 3327 | grep d01 -A 50` ![](./image/jstack2.png) java栈信息并不能提供定位问题的信息 5. dump分析 发生内存泄露内存溢出的情况时,通过java栈分析一般都无法出造成故障的原因,要分析出造成内存溢出的具体代码需要进行dump分析。 使用`jmap -dump:format=b,file=file_name pid`生成dump。 通过jvisualVM打开dump文件, ![](./image/jvisualvm.jpg) 可以看到实例数最多的几个类,确认是这几个类发生了内存泄漏。 本地调试和压测时也可以通过jvisualVM观察进程运行情况,但不建议在生产环境中直接连接运行进程。 6. 确认代码问题 结合代码分析CardInfo类中两个BigDecimal和三个Date对象,定位问题为CardInfo实例没有收回,同时发现ScheduledThreadPoolExecutor实例数与CardInfo实例数相同,基本确定是由于定时任务持有CardInfo实例导致导致CardInfo实例无法被GC回收。 ### 常用套路 java堆区分析的套路比较固定,基本都是观察实例数,存在内存泄漏问题的实例数往往会比正常的实例数高出很多,然后分析实例数较多的几个类之间的关系再结合代码确认最终问题。 ### 注意事项 - 提供web服务时,内存分配应该按照性能需求来确定,通过压测确定满足性能要求时,合适的内存大小。 - 提供计算服务时,内存分配应该按照计算的数据量来确定。 - 对于web服务,要尽量减少FullGC发生的次数,理想情况下保证两次更新间隔内不要发生FullGC。 - 分配内存大小建议控制在2-4g之前,内存太小容易出现FullGC,内存太大会导致dump分析困难。 - 发生内存泄漏但无法解决时,可以采用扩大内存和定期重启的方法。 - CMS和G1(jdk1.8)在处理FullGC时是单线程的,大内存情况下,会产生严重影响,因此还是需要通过配置优化避免FullGC的出现。

## 练习 ### 模拟程序 TreadLocalOOM类 启动脚本 ```sh nohup java -Xms50M -Xmx50M -XX:+PrintGC com.rk.jvm.ThreadLocalOOM > ThreadLocalOOM.out & ```