如果遇到线上问题,需要从几个方面排查问题。接口成功率,响应时间,CPU、内存占用率,IO性能
①如果是接口成功率为低甚至为0,可以考虑是不是出现了Java或者Mysql死锁问题。
②如果是接口响应时间长,可以考虑是不是文件IO频繁或者IO性能低,或者是慢SQL问题。
③如果接口响应一段时间后,接口响应失败,判断是否出现OOM异常。
④如果Java程序占用CPU过高是否存在空转,频繁gc,频繁上下文切换;如果内存占用率高是否存在堆外内存泄露问题。
Java死锁
测试代码:
public static void main(String[] args) { ReentrantLock lock1 = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); Thread t1 = new Thread(()->{ lock1.lock(); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } lock2.lock(); lock2.unlock(); lock1.unlock(); }); Thread t2 = new Thread(()->{ lock2.lock(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } lock1.lock(); lock1.unlock(); lock2.unlock(); }); t1.start(); t2.start(); }
查看方式
使用jstack pid
直接查看最下面有没有Found 1 deadlock,如果有,查看下面代码指代行数就可以发现出问题的代码了
Mysql死锁
测试死锁
test.user 表设置id主键索引 连接1,2的查询顺序为id=1,id=2;id=2,id=1,造成死锁 //连接1 set autocommit=0; select * from user where id=1 for update; select * from user where id=2 for update; //连接2 set autocommit=0; select * from user where id=2 for update;
执行上述语句之后,其中一个连接会阻塞,而另一个连接将会提示死锁信息:
Mysql死锁可以使用
show engine innodb status
命令查看Mysql死锁的情况,前提是Mysql开启了死锁检查解决方法:当出现死锁以后,有下面三种解决方法
1)开启系统死锁检测,发现死锁后,会主动回滚死锁中的某一个事务,让其他事务得以继续执行。执行set global innodb_deadlock_detect=on;
表示开启这个逻辑。Mysql默认开启。show variables like 'innodb_deadlock_detect';
命名查看此系统值。不过在高并发系统上,死锁检测可能导致SQL执行速度减慢,可能会关闭死锁检测。
2)设置锁超时时间。如果关闭死锁检查,Mysql线程还可以通过设置innodb_lock_wait_timeout
来限制SQL的锁等待时间。 关于MySQL死锁检测机制参数innodb_deadlock_detect设置的一点思考
①如果发生死锁,等待加锁的线程会在超时会退出并回滚,其他事务则可以继续执行。
命令 set global innodb_lock_wait_timeout=60
来设置锁等待过期时间。默认值是50s。通过show variables like 'innodb_lock_wait_timeout';
查看此系统值。
②但是innodb_lock_wait_timeout
只会回滚最后的造成锁等待超时的语句,并不会回滚整个事务。此时线程2还是持有id=1的锁并没有释放,线程1中的事务依然存在,死锁问题还可能存在。所以还需要设置innodb_rollback_on_timeout=on
,这样一旦出现锁超时的SQL整个事务都会回滚,可以很好地解决死锁问题。innodb_rollback_on_timeout属于只读变量,需要修改my.conf或者my.ini文件的内容并重启Mysql才生效。
Java程序可以通过设置 @Transactional(rollbackFor = Exception.class)
起到回滚整个事务的效果
③可以执行select * from information_schema.innodb_trx;
查看回滚前后的事务数量变化。
3)如果线上设置了比较长的锁等待时间,或者想尽快处理问题,可以执行kill mysql_thread_id
手动杀死其中mysql线程。
①执行select * from performance_schema.data_lock_waits;
查看当前锁等待信息。或者执行show status like 'InnoDB_row_lock%';
- REQUESTING_THREAD_ID:请求执行的mysql线程id
- BLOCKING_THREAD_ID:造成请求线程锁等待的线程id
- REQUESTING_ENGINE_TRANSACTION_ID:请求执行的事务id
- BLOCKING_ENGINE_TRANSACTION_ID:造成请求线程锁等待的事务id
此外该表还可以关联performance_schema.events_statements_current
查看请求事务正在等待执行的sql以及阻塞事务正在执行的sql。performance_schema.events_statements_current
表记录了当前事务的执行执行语句。
SELECT dlw.REQUESTING_THREAD_ID ,dlw.BLOCKING_THREAD_ID, dlw.REQUESTING_ENGINE_TRANSACTION_ID ,dlw.BLOCKING_ENGINE_TRANSACTION_ID,
esc.SQL_TEXT, esc2.SQL_TEXT FROM performance_schema.data_lock_waits dlw
inner join performance_schema.events_statements_current esc
on dlw.REQUESTING_THREAD_ID = esc.THREAD_ID
inner join performance_schema.events_statements_current esc2
on dlw.BLOCKING_THREAD_ID = esc2.THREAD_ID
②执行select trx_mysql_thread_id from information_schema.innodb_trx where trx_id in (select REQUESTING_ENGINE_TRANSACTION_ID from performance_schema.data_lock_waits)
查询所有锁等待mysql_thread_id
使用kill mysql_thread_id;
杀死其中的线程。
③此外还可以通过show engine innodb status
语句查看锁等待情况,找到存在死锁的线程并kill
以上需要用到的表描述
performance_schema.data_locks 当前事务持有的锁信息表
performance_schema.data_lock_waits 事务锁等待信息表
performance_schema.events_statements_current 当前事务执行SQL信息表
information_schema.innodb_trx 当前所有的事务信息表
Mysql锁等待超时导致查询失败
可以使用方法3)中的步骤查询performance_schema.data_lock_waits
表和show engine innodb status
查询造成锁等待超时的SQL。
JAVA服务突然无故挂掉
服务运行过程中突然死掉。需要重启才能运行。虽然设置了 -XX:HeapDumpPath
参数但是日志里面并没有错误信息,也没有出现OOM异常。执行jps出现process information unavailable,说明可能是系统重启或者服务突然挂掉。
这种情况可以查看系统日志文件/var/log/message
, 或者 dmesg
命令。
如果出现以下信息,而且pid与JAVA进程相同,那大概率是由于系统内存不足,导致JAVA服务被系统杀死的缘故。
linux java 进程被kill 如何排查原因 linux java进程自动退出
[15219579.748115] Out of memory: Kill process 30891 (java) score 112 or sacrifice child
[15219579.749110] Killed process 30891 (java), UID 997, total-vm:5716160kB, anon-rss:872648kB, file-rss:0kB, shmem-rss:0kB
OOM Killer:Linux操作系统内存不足时,会先触发内存回收机制释放内存,并将这部分被释放的内存分配给其他进程。如果内存回收机制不能处理系统内存不足的情况,则系统会触发OOM Killer(Out of Memory Killer)强制释放进程占用的内存,达到给系统解压的目的。对于系统关键进程(如init进程、内核线程等)是不能被杀死的,其它进程则通过oom_badness进行打分(0~1000),分数最高者被选中,一般占用系统内存最高的进程会被杀死。
原因:这种情况一般是设置了不合理的JVM参数,而且机器本身启动了较多的服务导致的。因为JVM默认初始内存是系统1/64 ,默认最大内存是系统1/4,默认当堆内存大于40%时,就会增大内存至最大内存。
如果在增大内存的过程中,操作系统就会找出需要杀死的进程,释放内存空间。这种情况下JAVA服务就有可能会被系统杀死而无故挂掉。
解决方法:为每个JVM服务设置合理的启动参数,最好将-Xms,-Xmx这两个参数设置一致。同时避免一台机器中启动多个JAVA服务。
JAVA假死问题
可能出现的原因:
①频繁Full GC导致STW,或者发生了内存泄露
②发生死循环,CPU占用率过高
③线程长时间阻塞,导致请求长时间无法响应
频繁Full GC
使用jstat -gcutil <pid>
或者jstat -gc<pid>
查看GC次数和时间
- YGC:从应用程序启动到采样时年轻代中gc次数
- YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
- FGC:从应用程序启动到采样时old代(全gc)gc次数
- FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
- GCT:从应用程序启动到采样时gc用的总时间(s)
如果YGC,FGC,GCT
的值比较大,这可能是由于发生JVM频繁GC而导致的STW问题
①获取GC日志:添加启动参数java -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCCause -Xloggc:/logs/gc.log
②查看堆内存使用情况:使用jmap -dump:live,format=b,file=/dump.hprof pid
命令生成多次dump文件。使用visualVM,jConsole,MAT等工具对比堆内存使用情况,分析是否有不合理对象或者大量生成的对象,例如Spring Bean大对象或者类静态变量。
③优化JVM配置,调整JVM堆内存参数或者使用更适合的垃圾收集器
JAVA发生死循环
如果JAVA服务CPU占用率接近100%,说明程序可能出现死循环。
①使用top
命令查看CPU异常的PID
②使用
top -Hp PID
查询进程中所有的线程CPU占用率③使用
jstack PID |grep ThreadID的十六进程
查看是哪个方法如果jstack pid
命令出现这种结果,可能是由于使用此命令的用户与启动java的命令不是同一个,也可能是由于执行jstack
时执行FULL GC导致无法连接。可以添加打印GC参数nohup java -XX:+PrintGC DemoTest > output.txt 2>&1 &
,然后到output.txt文件中查看jstack
线程信息。
6393: Unable to open socket file: target process not responding or HotSpot VM not loaded
The -F option can be used when the target process is not responding
线程长时间阻塞
如果发现线上请求过程非常漫长,几分钟都没有返回结果,但是测试一次请求只花费了5s左右,这时候需要查看代码是否发生了阻塞逻辑,导致并发访问下线程被阻塞了。
测试代码,模拟接口调用或者数据库加锁请求:
@RequestMapping("/block") public String block2() throws InterruptedException { synchronized ("aaa") { TimeUnit.SECONDS.sleep(5); } return "--block--"; }
使用arthas工具thread
命令查看所有http线程的执行情况,发现所有http请求都处于BLOCKED状态,说明它们都在等在锁释放。尤其是接口并发请求比较多,但是tomcat处理线程比较少的情况,等待时间会更长。使用thread ID
查看线程被阻塞的原因
Connection refused
①用户发送http请求,客户机先于服务器进行三次握手,
②握手成功,从半连接队列进入全连接队列
③tomcat获取指定端口的全连接队列中的socket数据
④Acceptor采用NIO方式,将socket数据交给工作线程处理
其中Springboot关于tomcat响应配置的参数包括:
server: tomcat: # 表示工作线程的最大数量,默认200 max-threads: 200 # 表示工作线程的最小数量,初始创建的线程数,默认10 min-spare-threads: 10 # 表示该tomcat服务最大连接数,一旦达到限制,操作系统仍然可以接受基于“acceptCount”属性的连接,默认8192 max-connections: 8192 # 表示连接如果大于最大连接数,还能接受的最大任务数,默认100 accept-count: 100 # 连接器在接受连接后等待显示请求 URI 行的时间。 connection-timeout: 20000
当连接数达到最大值max-connections后,系统会继续接收连接,进行排队,但不会超过accept-count的值。
Tomcat最大并发数取决于max-connections+accept-count。SpringBoot可以同时处理多少请求?
如果出现Connection refused
错误,可以查看以上配置是否合理。同时
使用ss -nlt|grep "port"
查看指定端口全连接队列容量
- Recv-Q表示(acceptCount)全连接队列目前长度
- Send-Q表示(acceptCount)全连接队列的容量。
使用netstat -nat | grep -i "port" | grep ESTAB | wc -l
查看指定端口成功建立的连接数
通过上述两个命令可以推算出请求是否是由于并发量过大导致的。
如果是因为某一时刻并发量过大导致无法访问,可以通过限流或者扩容来实现
Java OOM异常
常见的OOM异常详情说明: 10种常见OOM分析——手把手教你写bug
本文主要讲解Java heap space
异常,这个是OOM遇到比较多的异常
添加JVM启动参数,接收OOM文件-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/dump.hprof
示例程序:
public String test() throws InterruptedException { List<Integer[]> integers = new ArrayList<>(); int size = 10 * 1024 * 1024; while (true) { Integer[] ints = new Integer[size]; integers.add(ints); } }
提示如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /home/user/dump.hprof ...
Heap dump file created [1862040155 bytes in 38.078 secs]
MAT工具查看
找到hprof文件,使用MAT工具 => Leak Suspects 查看可能存在的内存泄露
histogram展示了Objects(类对象数量),Shallow Heap(浅堆大小),Retained Heap(深堆大小),从图中可以看出系统产生的Integer[]对象占用了大量内存,通过右键点击List Objects=>with incoming reference可以查看对象的引用链信息。利用MAT工具分析内存泄漏
Shallow Heap:表示对象本身占有的内存大小
Retained Heap:也叫保留集,表示释放对象占有的内存大小,说明如下;
如上图:SH表示浅堆,RH表示深堆,上图假设每个对象自身所占的内存都是10,也即所有对象浅堆都是10。
- 如果对象没有引用其他对象,那么它的深堆大小和浅堆相同,比如D,E,F,G;
- 如果单独引用了其他对象,则深堆大小等于自身和引用链中所有对象的大小之和,例如B,C的深堆大小是30;
- 如果和其他对象一起引用了其他其他对象,则深堆大小等于它单独引用对象大小之和,例如A虽然引用了B和C,但是释放A并不释放B的空间,只会释放C的空间,所以A的深堆大小等于A自身和C深堆之和。
dominator_tree展示了对象树信息,包括对象引用链中所有对象深堆和浅堆大小,从图中可以看出0xe37211e8这个ArrayList占用了99.27%的内存,但其本身占用内存比较小,点开查看引用对象信息,直到发现Integer[]对象深堆和浅堆大小都比较大,说明产生了很多Integer[]大对象。
Leaks可以直接展示存在内存泄漏的线程和大小,点击Details 查看造成内存泄漏的大对象信息和具体代码信息。
VisualVM查看
使用visualVM也可以查看内存泄露的信息,导入dump文件,点击选中的信息查看引起OOM线程,方法,变量信息
可能出现内存泄露的场景:GC日志分析 与 内存泄漏 与 OOM案例
①静态集合类,如将HashMap和ArrayList等申请成静态变量。那么容器中的所有对象都跟着不能被释放,从而导致内存的泄漏。
②资源未释放,如数据库连接,网络连接,IO连接等,如果没有显示的调用关闭函数,那么会导致大量的对象无法被回收,从而引起内存的泄漏。
③缓存泄漏,当对象被引入缓存的时候忘记清除。例如ThreadLocal
④内部非静态类,内部非静态类默认持有外部类的强引用,因此当内部类被长期饮用的时候,外部类对象将无法回收。
优化方案:
①尽早释放无用对象(置为null),提高GC回收效率。
②避免在循环中创建对象。
③获取数据库数据或者接口数据时尽量减少不必要字段的返回,占用内存空间。
参照:mysql死锁问题排查
这几种常见的 JVM 调优场景,你知道吗?
太坑了!Spring Boot 导致堆外内存泄露了!
数据库死锁排查思路
SQL优化经历
干货 | 阿里大神总结的JAVA 线上故障排查完整套路
JVM常用启动参数
Spring Boot 最大连接数和最大并发数是多少
服务假死