# 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使用率排序,找出对应进程。

pid=1537
2. 确定故障线程
使用`top -H -p pid`查看进程内的线程情况。

tid=1538
也可以`ps -mp pid -o THREAD,tid,time`查看更多的信息。

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`。

ps:linux中的tid对应的是jstack中的nid,jstack中的tid为java内的线程id。
如果java栈较复杂的情况可以通过`jstack pid > stack.txt`生成全量内容,进行分析。
4. 确认代码问题

原因为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`命令观察进程情况,找出对应进程。

`top`看到的内存包含了堆外内存,会比实际分配内存大,具体可查看gc日志。

3. 确定故障线程`top -H -p 3327 `

看到问题线程为VM Thread,说明jvm本身占用了大量的cpu资源,也可以定位为GC问题。
4. 查看线程执行情况`jstack 3327 | grep d01 -A 50`

java栈信息并不能提供定位问题的信息
5. dump分析
发生内存泄露内存溢出的情况时,通过java栈分析一般都无法出造成故障的原因,要分析出造成内存溢出的具体代码需要进行dump分析。
使用`jmap -dump:format=b,file=file_name pid`生成dump。
通过jvisualVM打开dump文件,

可以看到实例数最多的几个类,确认是这几个类发生了内存泄漏。
本地调试和压测时也可以通过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 &
```