爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      JDK的sql设计不合理导致的驱动类初始化死锁问题

      首页 知识中心 软件开发 文章详情页

      JDK的sql设计不合理导致的驱动类初始化死锁问题

      2024-09-24 06:30:42 阅读次数:154

      java在线,多线程同步

      问题描述

      当我们一个系统既需要mysql驱动,也需要oracle驱动的时候,在并发加载初始化这些驱动类的过程中产生死锁的可能性非常大,下面是一个模拟的例子,对于Thread2的实现其实是jdk里java.sql.DriverService的逻辑,也是我们第一次调用java.sql.DriverManager.registerDriver注册一个驱动实例要走的逻辑(jdk1.6下),不过这篇文章是使用我们生产环境的一个系统的线程dump和内存dump为基础进行分析展开的。

      import java.util.Iterator;
      
      import sun.misc.Service;
      
      public class Main {
          public static void main(String[] args) throws ClassNotFoundException {
              Thread1 thread1 = new Thread1();
              Thread2 thread2 = new Thread2();
              thread1.start();
              thread2.start();
          }
      }
      
      class Thread1 extends Thread {
          public void run() {
              try {
                  Class clazz = Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread()
                      .getContextClassLoader());
                  System.out.println(clazz);
              } catch (ClassNotFoundException e) {
              }
          }
      }
      
      class Thread2 extends Thread {
          public void run() {
              Iterator ps = Service.providers(java.sql.Driver.class);
              try {
                  while (ps.hasNext()) {
                      System.out.println(ps.next());
                  }
              } catch (Throwable t) {
      
              }
          }
      }
      

      如果以上代码运行过程中发现有线程一直卡死在Class.forName的调用里,那么说明问题已经重现了

      下面是问题进程线程dump的分析”thread dump”,还有这个进程的内存dump分析”heap dump”,这个后面展开分析的基础

      存疑点

      仔细看看上面的线程dump分析和内存dump分析里的线程分析模块,您可能会有如下两个疑惑:

      【为什么线程”Thread-0″一直卡在Class.forName的位置】:这有点出乎意料,做一个类加载要么找不到抛出ClassNotFoundException,要么找到直接返回,为什么会一直卡在这个位置呢?

      【明明”Thread-0″注册的是mysql驱动为什么会去加载Odbc的驱动类】:通过”Thread-0″在栈上看倒数第二帧展开看到传入Class.forName的参数是com.mysql.jdbc.Driver,然后展开栈上顺序第二帧,看到传入的参数是sun.jdbc.odbc.JdbcOdbcDriver,这意味着在对mysql驱动类做加载初始化的过程中又触发了JdbcOdbc驱动类的加载

      疑惑点解释

      疑惑二:

      第一个疑惑我们先留着,先解释下第二个疑惑,大家可以对照堆栈通过反编译rt.jar还有ojdbc6-11.2.0.3.0.jar看具体的代码

      驱动类加载过程简要介绍:

      当要注册某个sql驱动的时候是通过调用java.sql.DriverManager.registerDriver来实现的(注意这个方法加了synchronized关键字,后面解释第一个疑惑的时候是关键),而这个方法在第一次执行过程中,会在当前线程classloader的classpath下寻找所有/META-INF/services/java.sql.Driver文件,这个文件在mysql和oracle驱动jar里都有,里面写的是对应的驱动实现类名,这种机制是jdk提供的spi实现,找到这些文件之后,依次使用Class.forName(driverClassName, true, this.loader)来对这些驱动类进行加载,其中第二个参数是true,意味着不仅仅做一次loadClass的动作,还会初始化该类,即调用包含静态块的< clinit >方法,执行完之后才会返回,这样就解释了第二个疑惑,在mysql驱动注册过程中还会对odbc驱动类进行加载并初始化

      感想:

      其实我觉得这种设计有点傻,为什么要干和自己不相关的事情呢,画蛇添足的设计,首先类初始化的开销是否放到一起做并没有多大区别,其次正由于这种设计导致了今天这个死锁的发生

      疑惑一:

      现在来说第一个疑惑,为什么会一直卡在Class.forName呢,到底卡在哪里,于是再通过jstack -m 命令将jvm里的堆栈也打印出来,如下所示

      ----------------- 5738 -----------------
      0x003f67a2      _dl_sysinfo_int80 + 0x2
      0xb79a71ae      _ZN2os13PlatformEvent4parkEv + 0xee
      0xb7997acb      _ZN13ObjectMonitor4waitExbP6Thread + 0x5fb
      0xb7a73c53      _ZN18ObjectSynchronizer19waitUninterruptiblyE6HandlexP6Thread + 0x53
      0xb777eb34      _ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread + 0x74
      0xb777e288      _ZN13instanceKlass10initializeEP6Thread + 0x58
      0xb7821ad9      _Z28find_class_from_class_loaderP7JNIEnv_12symbolHandleh6HandleS2_hP6Thread + 0xb9
      0xb7807d99      JVM_FindClassFromClassLoader + 0x269
      0xb734c236      Java_java_lang_Class_forName0 + 0x116
      0xb433064a      * java.lang.Class.forName0(java.lang.String, boolean, java.lang.ClassLoader) bci:0 (Interpreted frame)
      0xb4328fa7      * java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader) bci:32 line:247 (Interpreted frame)
      0xb4328fa7      * sun.misc.Service$LazyIterator.next() bci:31 line:271 (Interpreted frame)
      0xb4329483      * java.sql.DriverService.run() bci:26 line:664 (Interpreted frame)
      0xb43263e6
      0xb77a4e31      _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1c1
      0xb79a6418      _ZN2os20os_exception_wrapperEPFvP9JavaValueP12methodHandleP17JavaCallArgumentsP6ThreadES1_S3_S5_S7_ + 0x18
      0xb77a4c5f      _ZN9JavaCalls4callEP9JavaValue12methodHandleP17JavaCallArgumentsP6Thread + 0x2f
      0xb780aace      JVM_DoPrivileged + 0x40e
      0xb734b95d      Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedAction_2 + 0x3d
      0xb433064a      * java.security.AccessController.doPrivileged(java.security.PrivilegedAction) bci:0 (Interpreted frame)
      0xb4328fa7      * java.sql.DriverManager.loadInitialDrivers() bci:31 line:506 (Interpreted frame)
      0xb432910d      * java.sql.DriverManager.initialize() bci:11 line:612 (Interpreted frame)
      0xb432910d      * java.sql.DriverManager.registerDriver(java.sql.Driver) bci:6 line:281 (Interpreted frame)
      0xb432910d      * com.mysql.jdbc.Driver.() bci:7 line:65 (Interpreted frame)
      0xb43263e6
      0xb77a4e31      _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1c1
      0xb79a6418      _ZN2os20os_exception_wrapperEPFvP9JavaValueP12methodHandleP17JavaCallArgumentsP6ThreadES1_S3_S5_S7_ + 0x18
      0xb77a4c5f      _ZN9JavaCalls4callEP9JavaValue12methodHandleP17JavaCallArgumentsP6Thread + 0x2f
      0xb77800c1      _ZN13instanceKlass27call_class_initializer_implE19instanceKlassHandleP6Thread + 0xa1
      0xb777ed8e      _ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread + 0x2ce
      0xb777e288      _ZN13instanceKlass10initializeEP6Thread + 0x58
      0xb7821ad9      _Z28find_class_from_class_loaderP7JNIEnv_12symbolHandleh6HandleS2_hP6Thread + 0xb9
      0xb7807d99      JVM_FindClassFromClassLoader + 0x269
      0xb734c236      Java_java_lang_Class_forName0 + 0x116
      0xb433064a      * java.lang.Class.forName0(java.lang.String, boolean, java.lang.ClassLoader) bci:0 (Interpreted frame)
      0xb4328fa7      * java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader) bci:32 line:247 (Interpreted frame)
      0xb4328fa7      * Thread1.run() bci:9 line:17 (Interpreted frame)
      

      我们看到其实正在做类的初始化动作,并且线程正在调用ObjectSynchronizer::waitUninterruptibly一直没返回,在看这方法的调用者instanceKlass1::initialize_impl,我们找到源码位置如下:

      void instanceKlass::initialize_impl(instanceKlassHandle this_oop, TRAPS) {
        // Make sure klass is linked (verified) before initialization
        // A class could already be verified, since it has been reflected upon.
        this_oop->link_class(CHECK);
      
        DTRACE_CLASSINIT_PROBE(required, instanceKlass::cast(this_oop()), -1);
      
        bool wait = false;
      
        // refer to the JVM book page 47 for description of steps
        // Step 1
        { ObjectLocker ol(this_oop, THREAD);
      
          Thread *self = THREAD; // it's passed the current thread
      
          // Step 2
          // If we were to use wait() instead of waitInterruptibly() then
          // we might end up throwing IE from link/symbol resolution sites
          // that aren't expected to throw.  This would wreak havoc.  See 6320309.
          while(this_oop->is_being_initialized() && !this_oop->is_reentrant_initialization(self)) {
              wait = true;
            ol.waitUninterruptibly(CHECK);
          }
      
          // Step 3
          if (this_oop->is_being_initialized() && this_oop->is_reentrant_initialization(self)) {
            DTRACE_CLASSINIT_PROBE_WAIT(recursive, instanceKlass::cast(this_oop()), -1,wait);
            return;
          }
      
          // Step 4
          if (this_oop->is_initialized()) {
            DTRACE_CLASSINIT_PROBE_WAIT(concurrent, instanceKlass::cast(this_oop()), -1,wait);
            return;
          }
      
          // Step 5
          if (this_oop->is_in_error_state()) {
            DTRACE_CLASSINIT_PROBE_WAIT(erroneous, instanceKlass::cast(this_oop()), -1,wait);
            ResourceMark rm(THREAD);
            const char* desc = "Could not initialize class ";
            const char* className = this_oop->external_name();
            size_t msglen = strlen(desc) + strlen(className) + 1;
            char* message = NEW_RESOURCE_ARRAY(char, msglen);
            if (NULL == message) {
              // Out of memory: can't create detailed error message
              THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
            } else {
              jio_snprintf(message, msglen, "%s%s", desc, className);
              THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
            }
          }
      
          // Step 6
          this_oop->set_init_state(being_initialized);
          this_oop->set_init_thread(self);
        }
        ...
      }
      

      类的初始化过程:

      当某个线程获得机会对某个类进行初始化的时候(请看上面的Step 6),会设置这个类的init_state属性为being_initialized(如果初始化好了会设置为fully_initialized,异常的话会设置为initialization_error),还会设置init_thread属性为当前线程,在这个设置过程中是有针对这个类提供了一把互斥锁的,因此当有别的线程进来的时候会被拦截在外面,如果设置完了,这把互斥锁也释放了,但是因为这个类的状态被设置了,因此并发问题也得到了解决,当另外一个线程也尝试初始化这个类的时候会判断这个类的状态是不是being_initialized,并且其init_thread不是当前线程,那么就会一直卡在那里,也就是此次线程dump的线程所处的状态,正在初始化类的线程会调用< clinit >方法,如果正常结束了,那么就设置其状态为fully_initialized,并且通知之前卡在那里等待初始化完成的线程,然他们继续往下走(下一个动作就是再判断下状态,发现完成了就直接return了)

      猜想 :

      在了解了上面的过程之后,于是我们猜测两种可能

      第一,这个类的状态还是being_intialized,还在while循环里没有跳出来

      第二,事件通知机制出现了问题,也就是pthread_cond_wait和pthread_cond_signal之间的通信过程出现了问题。

      不过第二种可能性非常小,比较linux久经考验了,那接下来我们验证其实是第一个猜想

      验证 :

      我们通过GDB attach的方式连到了问题机器上(好在机器没有挂),首先我们要找到具体的问题线程,我们通过上面的jstack -m命令看到了线程ID是5738,然后通过info threads找到对应的线程,并得到它的序号14

      (gdb) info threads
      17 process 5724 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      16 process 6878 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      15 process 5739 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      14 process 5738 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      13 process 5737 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      12 process 5736 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      11 process 5735 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      10 process 5734 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      9 process 5733 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      8 process 5732 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      7 process 5731 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      6 process 5730 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      5 process 5729 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      4 process 5728 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      3 process 5727 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      2 process 5726 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      1 process 5725 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      

      然后通过thread 14切换到对应的线程,并通过bt看到了如下的堆栈,正如我们想象的那样,正在做类的初始化,一直卡在那里

      (gdb) thread 14
      [Switching to thread 14 (process 5738)]#0 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      (gdb) bt
      #0 0x003f67a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
      #1 0x005e0d76 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib/tls/i686/nosegneg/libpthread.so.0
      #2 0x005e13ee in pthread_cond_wait@GLIBC_2.0 () from /lib/tls/i686/nosegneg/libpthread.so.0
      #3 0xb79a71ae in os::PlatformEvent::park () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #4 0xb7997acb in ObjectMonitor::wait () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #5 0xb7a73c53 in ObjectSynchronizer::waitUninterruptibly () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #6 0xb777eb34 in instanceKlass::initialize_impl () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #7 0xb777e288 in instanceKlass::initialize () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #8 0xb7821ad9 in find_class_from_class_loader () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #9 0xb7807d99 in JVM_FindClassFromClassLoader () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/server/libjvm.so
      #10 0xb734c236 in Java_java_lang_Class_forName0 () from /home/opt/taobao/install/jdk1.6.0_33/jre/lib/i386/libjava.so
      #11 0xb433064a in ?? ()
      #12 0x0813b120 in ?? ()
      #13 0x70aaa690 in ?? ()
      #14 0x70aaa6a0 in ?? ()
      #15 0x00000001 in ?? ()
      #16 0x70aaa698 in ?? ()
      #17 0x00000000 in ?? ()
      

      我们通过f 6选择第7帧,在通过disassemble反汇编该帧,也就是对instanceKlass::initialize_impl ()这个方法反汇编

      0xb777eaed <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+45>:  lea    0xfffffff4(%ebp),%esp    //将%ebp偏移0xfffffff4位置的值存到%esp栈顶,然后下面的pop操作存到%ebx
      0xb777eaf0 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+48>:  pop    %ebx
      0xb777eaf1 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+49>:  pop    %esi
      0xb777eaf2 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+50>:  pop    %edi
      0xb777eaf3 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+51>:  pop    %ebp
      0xb777eaf4 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+52>:  ret
      0xb777eaf5 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+53>:  push   $0x1
      0xb777eaf7 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+55>:  lea    0xffffffd8(%ebp),%edx
      0xb777eafa <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+58>:  push   %esi
      0xb777eafb <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+59>:  push   %ebx
      0xb777eafc <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+60>:  push   %edx
      0xb777eafd <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+61>:  call   0xb7a73a80 <_ZN12ObjectLockerC1E6HandleP6Threadb>
      0xb777eb02 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+66>:  add    $0x10,%esp
      0xb777eb05 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+69>:  xor    %eax,%eax
      0xb777eb07 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+71>:  test   %ebx,%ebx
      0xb777eb09 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+73>:  je     0xb777eb0d <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+77>
      0xb777eb0b <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+75>:  mov    (%ebx),%eax      //将%ebx的值移到%eax
      0xb777eb0d <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+77>:  cmpl   $0x4,0xe0(%eax)  //对比%eax偏移0xe0位置的值和0x4(这个值其实就是上面提到的being_initialized状态,这就说明了%eax偏移0xe0位置其实存的就是初始化类的初始化状态)
      0xb777eb14 <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+84>:  jne    0xb777eb4f <_ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread+143>
      

      从上面的注释我们其实得出了,我们要看当前类的初始化状态,那就是看eax寄存器偏移0xe0的位置的值,而eax其实就是ebp寄存器偏移0xfffffff4位置的值,于是我们通过如下地址内存查到得到是4

      (gdb) x $ebp + 0xfffffff4
      0x70aaa45c: 0x71af2180
      (gdb) x/3w 0x71af2180 + 0xe0
      0x71af2260: 0x00000004  0x0813c800  0x0000001a
      

      而4其实代表的就是being_initialized这个状态,代码如下

      enum ClassState {
      unparsable_by_gc = 0,
      allocated,
      loaded,
      linked,
      being_initialized,
      fully_initialized,
      initialization_error
      };
      

      从这于是我们验证了第一个猜想,其实是状态一直没有变更,因此一直卡在那里,为了更进一步确认这个问题,要是我们能找到该类的init_thread线程id就更清楚了,拿到这个ID我们就能看到这个线程栈,就知道它在干什么了,但是很遗憾,这个很难获取到,至少我一直没有找到办法,因为线程ID在线程对象里一直没有存,都是调用的os函数来获取的,得换个思路。

      突然发现instanceKlass.hpp代码中得知两个属性原来是相邻的(init_state和init_thread),于是断定下一个地址的值就代表是这个线程对象了,但是其属性何其多,找到想要的太不易了,最主要的是还担心自己看的代码和服务器上的jvm代码不一致,这样更蛋疼了,于是继续查看Thread.hpp中的JavaThread类,找到个关键字0xDEAD-2=0xDEAB,这个有可能是volatile TerminatedTypes _terminated属性的值,于是把线程对象打印出来,果然查到了关键字0xDEAB

      (gdb) x/100w 0x0813c800
      0x813c800: 0xb7bc06e8 0x00000000 0x00000000 0x00000000
      0x813c810: 0x0813c488 0x0813d2c8 0x00000000 0x00000000
      0x813c820: 0x080f9bf8 0x080f8b50 0x70a59b60 0x00000000
      0x813c830: 0x00000000 0x00000000 0x00000000 0x00000000
      0x813c840: 0x00014148 0x00000505 0x00000000 0x00000000
      0x813c850: 0x00000000 0x00000000 0x00000000 0x3f800021
      0x813c860: 0x00000001 0x00000023 0x3f800021 0x0001b530
      0x813c870: 0x00000000 0x00000000 0x00000000 0x080ffdc0
      0x813c880: 0x00000001 0x00000000 0x080ffe24 0x00000014
      0x813c890: 0x00000031 0x00000000 0x00000000 0x0813dab0
      0x813c8a0: 0x0813c428 0x0813ce98 0x70a5b000 0x00051000
      0x813c8b0: 0x00000000 0xffffffff 0x00000000 0x080ffdc0
      0x813c8c0: 0x00002bad 0x0813d400 0x0813d500 0x0813d700
      0x813c8d0: 0x0813d800 0x00000000 0x00000000 0x104aa1ad
      0x813c8e0: 0x544a5ab2 0x32378fc7 0x00008767 0x00000000
      0x813c8f0: 0x00000000 0x00000000 0x0ee9547d 0x00000000
      0x813c900: 0x00000000 0x00000000 0x0813b000 0x75878760
      0x813c910: 0x70a59a94 0x00000000 0x70a59abc 0xb7829020
      0x813c920: 0xb7bb7100 0x00000000 0x00000000 0x00000000
      0x813c930: 0x00000000 0x00000000 0x00000000 0x00000000
      0x813c940: 0x00000000 0x00000000 0x00000000 0x00000000
      0x813c950: 0x00000000 0x00000000 0x00000000 0x0000000a
      0x813c960: 0x0813da98 0x00000000 0x0000deab 0x00000001
      0x813c970: 0x00000000 0x00000000 0x00000002 0x00000000
      0x813c980: 0x00000000 0x00000000 0x00000000 0x00000000
      

      因此顺着这个属性继续往上找,找到了_thread_state表示线程状态的值(向上偏移三个字),0x0000000a,即10,然后查看代码知道原来线程是出于block状态

      public:
      volatile JavaThreadState _thread_state;
      private:
      ThreadSafepointState *_safepoint_state;
      address _saved_exception_pc;
      volatile TerminatedTypes _terminated;
      enum JavaThreadState {
      _thread_uninitialized = 0, // should never happen (missing initialization)
      _thread_new = 2, // just starting up, i.e., in process of being initialized
      _thread_new_trans = 3, // corresponding transition state (not used, included for completness)
      _thread_in_native = 4, // running in native code
      _thread_in_native_trans = 5, // corresponding transition state
      _thread_in_vm = 6, // running in VM
      _thread_in_vm_trans = 7, // corresponding transition state
      _thread_in_Java = 8, // running in Java or in stub code
      _thread_in_Java_trans = 9, // corresponding transition state (not used, included for completness)
      _thread_blocked = 10, // blocked in vm
      _thread_blocked_trans = 11, // corresponding transition state
      _thread_max_state = 12 // maximum thread state+1 - used for statistics allocation
      };
      

      这样一来查看下线程dump,发现”Thread-1″正好处于BLOCKED状态,也就是说Thread-1就是那个正在对mysql驱动类做初始化的线程,这说明Thread-0和Thread-1成功互锁了

      于是我们展开【Thread-1】,看到- waiting to lock (a java.lang.Class for java.sql.DriverManager),该线程正在等待java.sql.DriverManager类型锁,而blocked在那里,而这个类型锁是被【Thread-0】线程持有的,从【Thread-1】这个线程堆栈来看它其实也是在做Class.forName动作,并且通过”Thread-1″,展开第四帧我们可以看到其正在对加载sun.jdbc.odbc.JdbcOdbcDriver

      问题现场遐想:

      于是我们大胆设想一个场景,【Thread-1】先获取到初始化sun.jdbc.odbc.JdbcOdbcDriver的机会,然后在执行sun.jdbc.odbc.JdbcOdbcDriver这个类的静态块的时候调用DriverManager.registerDriver(new Driver());,而该方法之前已经提到了是会加同步锁的,再想象一下,在这个这个静态块之前,并且设置了sun.jdbc.odbc.JdbcOdbcDriver类的初始化状态为being_initialized之后,【Thread-0】这个线程执行到了卡在的那个位置,并且我们从其堆栈可以看出它已经持有了java.sql.DriverManager这个类型的锁,因此这两个线程陷入了互锁状态

      解决方案

      解决方案目前想到的是将驱动类的加载过程变成单线程加载,不存在并发情况就没问题了。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:http://ifeve.com/jdk-sql-deadlock/,作者:并发编程网,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:前端工作总结285-重置新增的mode

      下一篇:ClearspaceMUCTranscriptManager的roomEvents是否存在并发问题

      相关文章

      2024-09-25 10:14:21

      JAVA的内存模型及结构

      JAVA的内存模型及结构

      2024-09-25 10:14:21
      java在线
      2024-09-25 10:13:46

      《Flink官方文档》示例总览

      Java 的示例项目和Scala 的示例项目指导了构建Maven和SBT项目,并包含了一个单词计数程序的简单实现。

      2024-09-25 10:13:46
      Apache , java在线
      2024-09-24 06:30:20

      Java8并发教程:Threads和Executors

      Java8并发教程:Threads和Executors

      2024-09-24 06:30:20
      java在线
      2024-06-27 09:21:24

      Java 网络教程: InetAddress

      InetAddress 是 Java 对 IP 地址的封装。这个类的实例经常和 UDP DatagramSockets 和 Socket,ServerSocket 类一起使用。

      2024-06-27 09:21:24
      java在线
      2024-06-27 09:21:24

      泥瓦匠聊并发编程基础篇:线程中断和终止

      线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 interrupt() 方法,使得该线程中断标志位属性改变。

      2024-06-27 09:21:24
      多线程同步
      2024-06-27 09:21:24

      泥瓦匠聊并发编程:线程与多线程必知必会(基础篇)

      线程(Thread)是一个对象(Object)。Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。

      2024-06-27 09:21:24
      多线程 , 多线程同步
      2024-06-27 09:20:52

      Java网络教程: InetAddress

      InetAddress是ip地址的java表示方式。这个类的实例也可以用在UDP DatagramSockets、普通Socket类和ServerSocket类。

      2024-06-27 09:20:52
      java在线
      2023-03-21 10:32:27

      并发性能优化 &#8211; 降低锁粒度

      在高负载多线程应用中性能是非常重要的。为了达到更好的性能,开发者必须意识到并发的重要性。当我们需要使用并发时, 常常有一个

      2023-03-21 10:32:27
      java在线 , the public
      2023-03-21 10:32:27

      非阻塞算法

      在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法。在绝大多数项目中,在算法中如果一个线程的挂起没有导致其它的线程挂起,我们就说这个算法是非阻塞的。为了

      2023-03-21 10:32:27
      数据结构 , 多线程同步
      2023-03-21 10:32:10

      JAVA面试题100问第一部分

      注:由于原文太长,这只是大概三分一的部分,即翻译至第五页倒数第三个问题。以下是面试时常问到的JAVA面试题,能让你对JAVA面试有基本的了解。根据我个人的经验,一个好的面试官在面试的时候是不会事先准备一列问题清单

      2023-03-21 10:32:10
      java在线
      查看更多
      推荐标签

      作者介绍

      separate_out
      天翼云用户

      文章

      16

      阅读量

      5441

      查看更多

      最新文章

      Java8并发教程:Threads和Executors

      2024-09-24 06:30:20

      Java 网络教程: InetAddress

      2024-06-27 09:21:24

      泥瓦匠聊并发编程基础篇:线程中断和终止

      2024-06-27 09:21:24

      泥瓦匠聊并发编程:线程与多线程必知必会(基础篇)

      2024-06-27 09:21:24

      Java网络教程: InetAddress

      2024-06-27 09:20:52

      并发性能优化 &#8211; 降低锁粒度

      2023-03-21 10:32:27

      查看更多

      热门文章

      Java IO教程

      2022-11-08 07:33:17

      Byte Buddy 教程(1.1)-编写一个安全的库

      2023-02-24 10:12:47

      Java内存模型

      2023-03-21 10:32:10

      Java IO: FileReader和FileWriter

      2022-11-08 07:33:31

      并发队列-无界阻塞队列LinkedBlockingQueue原理探究

      2023-03-21 10:31:48

      走进Java

      2023-02-15 08:25:21

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      Java IO: ByteArrayInputStream

      Java IO: PipedInputStream

      你应该知道的 volatile 关键字

      Java构造器必知必会

      JUC的AQS学习-ReentrantLock源代码分析

      Java ThreadLocal的使用

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号