JVM系列(三十二) JVM调优实战-Jstack 死锁必要条件及原因分析

274 阅读5分钟

Jstack日志分析定位死锁问题

前面我们讲解了Jstack查看线程状态,通过jstack日志,我们可以看到线程的启动状态,包括Runnable,Block及Waiting

那么从jstack 日志, 我们可以分析出来什么? 或者从日志里我们是否可以解决线程阻塞的问题?

1.Jstack日志信息

Jstack日志又叫 线程堆栈Dump 文件, 我们执行 jstack -pid 就是把现在系统运行的线程的堆栈打印出来, 下面我们讲解下 日志如何分析

  • Runnable,线程处于执行中, 运行的线程 不需要关注
  • Deadlock,死锁(重点关注)
  • Blocked,线程被阻塞(重点关注)
  • Parked,停止
  • Locked,对象加锁
  • Waiting,线程正在等待
  • Waiting to lock 等待上锁
  • Object.wait(),对象等待中
  • Waiting for monitor entry 等待获取监视器(重点关注)
  • Waiting on condition,等待资源(重点关注)

如果线程打印出来出来需要重点关注的状态, 这些线程就是有问题的,是需要我们排查的,否则就会导致系统长时间等待,无响应的场景,或者造成CPU过高的问题, 下面我们一一讲解下 这几种状态及问题定位和解决方案

2.DeadLock 死锁

什么是DeadLock死锁?

死锁就是 相互依赖的多方,因为竞争资源,都无法得到相应的资源, 而导致程序假死, 表现在项目中就是最终程序无响应

死锁的四个必要条件

  • 互斥条件
    • 资源互斥使用,资源的分配具有排他性,即当资源对象被一个线程使用(占有)时,别的线程都无法使用,只能够等待
  • 该资源不可剥夺
    • 资源不可剥夺,也就是说资源无法被资源请求者强制抢夺,所以占有者在使用完资源前,无法被其他进程强行夺走
    • 必须等待资占有者主动释放,可剥夺资源的竞争是不会引起死锁的
  • 请求和保持
    • 进程中如果已经存在了一部分资源,当资源请求者在请求其他的资源的同时
    • 请求者还保持对自己原有资源的占有,不释放持有的资源
  • 循环等待
    • 即存在一个等待队列,每一个进程所需要的资源,被下一个进程持有不释放,如T1等待占有T2的资源,T2等待占有T3的资源,T3等待占有T1的资源, 多方互相循环, 形成了一个等待环路。

3.如何避免死锁

上面我们了解了死锁的四个必要条件,那么在实际的项目中,或者开发过程中,我们如何避免死锁呢?

我们只需要打破其中任意一个条件即可避免死锁问题,下面我们讲解下如何打破这四个条件

  • 破坏 互斥条件 无法成立
    • 该条件是资源本身决定的,如果资源不互斥,那么就是每个进程都可以使用,那么也就不存在死锁了,因为每个进程都能拿到资源
  • 破环 资源不可剥夺 条件
    • 既然要破坏资源不可剥夺条件,那么我们让资源可以被剥夺,也就是允许其他进程在请求资源时候,对该资源进行抢夺,这就破坏了不可剥夺的条件
    • 方法一 如果资源请求者,请求资源无法获取资源,那么它就要释放自身持有的资源,以便别人可以使用自己的资源
    • 方法二 可以设置线程优先级,对资源来说,线程优先级高的进程,优先分配资源
    • 方法三 加时请求,线程再请求资源的时候,要有时间限制,比如过了多久,还没有拿到该资源的时候,线程主动不再请求该资源,同时释放自己占有的锁。
  • 破坏 请求和保持 条件
    • 破坏请求和保持条件,就是不允许资源请求者在自己已经获取部分资源的情况下,申请其他资源,组织进程持有资源的同时,请求其他资源
    • 方法一 一次性分配,就是进程既然无法在持有资源情况下再次申请其他资源,那么要么不分配,要么一次性分配,如果现在资源充足,那么就给你所有的资源,你也不需要再去请求其他资源,也就不会请求保持
    • 方法二 先释放后申请,资源请求者再对新的资源提出申请前,必须释放自己占有的资源,这样也破坏了请求和保持条件
  • 破坏 循环等待 条件
    • 循环等待条件,是存在等待环路,我们如果让资源统一分配,把系统所有的资源全都编号,统一分配,所有的进程必须按照资源编号的顺序进行申请,这样就不会出现 等待环路,自然也不会出现死锁条件

本文 讲解我们分析 jstack 日志的时候,对于死锁问题的处理,分析了死锁产生的条件和常用的避免死锁的方法,破坏其四个必要条件避免死锁,下一篇,我们来分析下 死锁问题定位代码和jstack日志