zoukankan      html  css  js  c++  java
  • 一篇文章搞清JVM死锁问题及排查

    关于死锁,一直是面试和日常开发中的熟悉话题,本文将进行一下探讨:

    1. 什么是死锁
    2. 出现死锁的原因
    3. 如何避免死锁
    4. 代码中死锁问题怎么排查

    @

    1. 什么是死锁

    死锁是指两个或两个以上的进程或线程,在执行过程中,由于竞争资源而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去

    划重点:两个或两个以上进程或线程,竞争资源,最终阻塞无法进行下去

    这里可能会问:外力作用是什么外力?资源剥夺;撤销进程;进程回退等

    2. 出现死锁的原因

    1. 系统资源不足;

    2. 进程推进顺序非法

    3. 系统资源分配不当。

      另外,信号量使用不当也会造成死锁。比如A等B消息,B等A的消息

    死锁产生的有4个必要条件
    (1) 互斥条件:一个资源每次只能被一个进程使用。
    (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

    3. 如何预防和避免死锁

    • 预防:

    死锁的预防基本思想打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。

    比如:

    打破互斥条件:允许进程同时访问某些资源

    打破不剥夺条件:允许进程从占有者占有的资源中强行剥夺一些资源

    打破请求与保持条件:进程在运行前一次性地向系统申请它所需要的全部资源

    打破循环等待条件:实行资源有序分配策略

    • 避免:
    1. 加锁顺序(线程按照一定的顺序加锁)
    2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
    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方式排查

    1. 查找程序运行端口

      > jps -l 
      18714 sun.tools.jps.Jps
      18703 jvm.DeadLock
      
    2. jstack打印堆栈信息,发现死锁存在的位置,进行排查

      > jstack -l 18703
      

    4.2.2 jconsole方式排查

    mac下:输入jconsol命令通过可视化界面连接

    image-20200612232936748

    选择线程,监测死锁。会将死锁的线程信息都展示出来

    4.2.3 jvisualvm方式

    用命令行召唤出jconsole,选择对应进程即可直观看到死锁的存在

  • 相关阅读:
    [跟我学spring学习笔记][更多DI知识]
    [跟我学spring][Bean的作用域]
    [跟我学spring学习笔记][DI循环依赖]
    [跟我学spring学习笔记][IoC]
    [跟我学Spring学习笔记][DI配置与使用]
    [Spring入门学习笔记][静态资源]
    [Spring入门学习笔记][Spring的AOP原理]
    介绍map.entry接口
    hashmap的遍历方法
    java中的队列
  • 原文地址:https://www.cnblogs.com/valjeanshaw/p/13124689.html
Copyright © 2011-2022 走看看