一篇文章搞清JVM死锁问题及排查
关于死锁,一直是面试和日常开发中的熟悉话题,本文将进行一下探讨:
- 什么是死锁
- 出现死锁的原因
- 如何避免死锁
- 代码中死锁问题怎么排查
@
1. 什么是死锁
死锁是指两个或两个以上的进程或线程,在执行过程中,由于竞争资源而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
划重点:两个或两个以上进程或线程,竞争资源,最终阻塞无法进行下去
这里可能会问:外力作用是什么外力?资源剥夺;撤销进程;进程回退等
2. 出现死锁的原因
系统资源不足;
进程推进顺序非法
系统资源分配不当。
另外,信号量使用不当也会造成死锁。比如A等B消息,B等A的消息
死锁产生的有4个必要条件
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
3. 如何预防和避免死锁
- 预防:
死锁的预防基本思想打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
比如:
打破互斥条件:允许进程同时访问某些资源
打破不剥夺条件:允许进程从占有者占有的资源中强行剥夺一些资源
打破请求与保持条件:进程在运行前一次性地向系统申请它所需要的全部资源
打破循环等待条件:实行资源有序分配策略
- 避免:
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测
阿里巴巴中最新的开发规约,里面有对避免死锁的说明,具体如下:
【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。 说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。
4. 实战JVM死锁问题排查
4.1 死锁代码案例
按照死锁产生原则,可写出一个产生死锁的程序
public class DeadLock { //创建两个对象,用两个线程分别先后独占 private Boolean flag1 = true; private Boolean flag2 = false; public static void main(String[] args) { DeadLock deadLock = new DeadLock(); new Thread(new Runnable() { @Override public void run() { System.out.println("线程1开始,作用是当flag1 = true 时,将flag2也改为 true"); synchronized (deadLock.flag1){ if(deadLock.flag1){ try{ //睡眠1s ,模拟业务执行耗时,并保证两个线程进入死锁状态 Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("flag1 = true,准备锁住flag2..."); synchronized (deadLock.flag2){ deadLock.flag2 = true; } } } } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("线程2开始,作用是当flag2 = false 时,将flag1也改为 false"); synchronized (deadLock.flag2){ if(!deadLock.flag2){ try{ //睡眠1s ,模拟业务执行耗时,并保证两个线程进入死锁状态 Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("flag2 = false,准备锁住flag1..."); synchronized (deadLock.flag1){ deadLock.flag1 = false; } } } } }).start(); } }
以上代码,可以用一个死锁的图解释。线程1独占对象1,想要访问对象2,而对象2此时已经独占对象2,在等待对象1的资源释放,此时线程1因无法获取到对象2而无法向下执行,因此没法释放对象1,线程2同理,造成了死锁状态,两个线程都阻塞在等待资源处
4.2 死锁问题JVM工具排查
4.2.1 jps+jstack方式排查
查找程序运行端口
> jps -l 18714 sun.tools.jps.Jps 18703 jvm.DeadLock
jstack打印堆栈信息,发现死锁存在的位置,进行排查
> jstack -l 18703
4.2.2 jconsole方式排查
mac下:输入jconsol命令通过可视化界面连接
选择线程,监测死锁。会将死锁的线程信息都展示出来
4.2.3 jvisualvm方式
用命令行召唤出jconsole,选择对应进程即可直观看到死锁的存在