问题
前端时间帮同事分析了一个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();}}
}