java类静态初始化死锁问题

news/2024/12/11 15:25:23/

问题

前端时间帮同事分析了一个IO线程阻塞问题,该问题导致服务端无法处理任何请求,只能进行重启解决;事发时运维dump了下栈信息,堆栈信息如下图:

从上面可以看到io线程都阻塞于Object.wait(),具体是执行Class.forName()进行类加载时导致的阻塞,但是其中有两个线程阻塞点不一样,分别如下图:

分析

从问题图片可以看出:这两个阻塞点都和DriverManager的静态方法有关系,其中exec-2线程阻塞在DriverManager的静态代码初始化过程中,代码如下:

java">public class DriverManager {// List of registered JDBC driversprivate final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();private static volatile int loginTimeout = 0;private static volatile java.io.PrintWriter logWriter = null;private static volatile java.io.PrintStream logStream = null;// Used in println() to synchronize logWriterprivate final static  Object logSync = new Object();/* Prevent the DriverManager class from being instantiated. */private DriverManager(){}/*** Load the initial JDBC drivers by checking the System property* jdbc.properties and then use the {@code ServiceLoader} mechanism*/static {loadInitialDrivers();//阻塞点println("JDBC DriverManager initialized");}......................
}

而exec-3线程阻塞在调用DriverManager.registerDriver(),代码如下:

java">public class Driver{static {try {// moved the registerDriver from the constructor to here// because some clients call the driver themselves (I know, as// my early jdbc work did - and that was based on other examples).// Placing it here, means that the driver is registered once only.register();} catch (SQLException e) {throw new ExceptionInInitializerError(e);}}
.......
public static void register() throws SQLException {if (isRegistered()) {throw new IllegalStateException("Driver is already registered. It can only be registered once.");}Driver registeredDriver = new Driver();DriverManager.registerDriver(registeredDriver);//line727,阻塞点Driver.registeredDriver = registeredDriver;}
.....
}

以此依据来分析下现象:

1.大多数线程都是阻塞在Class.forName,因为Class.forName会调用native方法对类进行静态初始化,该初始化过程是加锁的,主要是为了避免重复对类进行加载;

2.exec-3阻塞在org.postgresql.Driver类静态初始化过程(static代码块)中的对DriverManager.registerDriver()方法的调用中,说明其在等待DriverManager的静态初始化完成;

3.exec-2阻塞在DriverManager的静态初始化过程执行loadInitialDrivers()方法中,该方法会完成对Driver所有的实现类的加载和初始化,也就是在该过程中会进行了org.postgresql.Driver的初始化,那么就和现象2中的逻辑冲突,从而导致死锁;

因此大致可以判断整个死锁过程应该是类加载死锁问题,该死锁问题最大的一个特征就是:两个类在进行类初始化过程中,会对对方进行一次类加载,如果这两个类同时进行类初始化,就会出现死锁;

验证

验证代码:

java">package test.common;public class 	StaticInitialDeadLock {public static void main(String[] args) throws InterruptedException {new Thread(()->{try {Class.forName("test.common.TestA");} catch (Exception e) {}}).start();new Thread(()->{try {Class.forName("test.common.TestB");} catch (Exception e) {}}).start();Thread.sleep(100000);}
}class TestA {static {try {System.out.println("pga initial");Thread.sleep(1000);Class.forName("test.common.TestB");System.out.println("pga initial finish");} catch (Exception e) {}}public static void test() {System.out.println("pga");}
}class TestB {static {try {System.out.println("pgb initial");Thread.sleep(1000);Class.forName("test.common.TestA");System.out.println("pgb initial finish");} catch (Exception e) {e.printStackTrace();}}
}


http://www.ppmy.cn/news/1554250.html

相关文章

Xcode模拟器运行报错:The request was denied by service delegate

Xcode模拟器运行报错&#xff1a;The request was denied by service delegate 造成的原因: &#xff08;1&#xff09;新的苹果M系列芯片的Mac电脑 &#xff08;2&#xff09;此电脑首次安装启动Xcode的应用程序 &#xff08;3&#xff09;此电脑未安装Rosetta 2 解决方法: …

【蓝桥杯每日一题】X 进制减法

X 进制减法 2024-12-6 蓝桥杯每日一题 X 进制减法 贪心 进制转换 题目大意 进制规定了数字在数位上逢几进一。 XX 进制是一种很神奇的进制, 因为其每一数位的进制并不固定&#xff01;例如说某 种 XX 进制数, 最低数位为二进制, 第二数位为十进制, 第三数位为八进制, 则 XX 进制…

Xilinx IDDR和 ODDR原语使用和仿真

// IODDR 回环 &#xff0c;使用 SAME_EDGE 模式&#xff0c;注意的是 从 ODDR输出 时钟需要偏移90度&#xff0c;不然数据 上下沿采样错误 // 数据从 IDDR 输出时&#xff0c;最好将 数据和 frame 有效信号 同步在偏移90度的时钟下 // 本测试内容 是 50MHZ 的 8bit &#xff0…

【jvm】为什么要有GC

目录 1. 自动内存管理2. 提升程序稳定性3. 优化性能4. 跨平台能力5. 分代回收策略 1. 自动内存管理 1.JVM中的GC机制负责自动管理内存&#xff0c;这意味着开发人员不需要手动分配和释放内存。2.这一特性大大简化了Java程序的内存管理&#xff0c;降低了内存泄漏和内存溢出等问…

深信服ATRUST与锐捷交换机端口链路聚合的配置

深信服ATRUST业务口原来只配置使用一个电口&#xff0c;近期出现流量达到800-900M接近端口的极限带宽。由于设备没有万光口&#xff0c;于是只好用2个光口来配置链接聚合。 下需附上深信服ATRST端口配置的截图&#xff0c;由于深信服ATRUST与锐捷交换机端口只共同支持源mac目的…

SQL UCASE() 函数:转换字符串为大写

SQL UCASE() 函数&#xff1a;转换字符串为大写 概述 在SQL中&#xff0c;UCASE() 函数用于将字符串中的所有字符转换为大写。这是一个非常实用的函数&#xff0c;尤其在处理大量文本数据时&#xff0c;确保数据的一致性和准确性。本文将详细介绍UCASE() 函数的用法、示例以及…

easyocr配置及相关训练

easyocr配置及相关训练 1&#xff0c;相关链接2&#xff0c;安装配置3&#xff0c;官方模型测试4&#xff0c;自定义数据集训练及测试&#xff08;1&#xff09;标签转换脚本&#xff08;2&#xff09;生成lmdb数据格式&#xff08;重要&#xff09;&#xff08;3&#xff09;预…

【系统设计】高可用之缓存基础

缓存的缘起 使用缓存的主要原因包括提高系统性能、降低数据库负载、提升用户体验和保证系统可用性。‌ 在计算机体系结构中&#xff0c;由于处理器和存储器的处理时间不匹配&#xff0c;在处理器和一个较大较慢的设备之间插入一个更小更快的存储设备&#xff08;如高速缓存&a…