问题
前端时间帮同事分析了一个IO线程阻塞问题,该问题导致服务端无法处理任何请求,只能进行重启解决;事发时运维dump了下栈信息,堆栈信息如下图:
从上面可以看到io线程都阻塞于Object.wait(),具体是执行Class.forName()进行类加载时导致的阻塞,但是其中有两个线程阻塞点不一样,分别如下图:
分析
从问题图片可以看出:这两个阻塞点都和DriverManager的静态方法有关系,其中exec-2线程阻塞在DriverManager的静态代码初始化过程中,代码如下:
public class DriverManager {
// List of registered JDBC drivers
private 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 logWriter
private 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(),代码如下:
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中的逻辑冲突,从而导致死锁;
因此大致可以判断整个死锁过程应该是类加载死锁问题,该死锁问题最大的一个特征就是:两个类在进行类初始化过程中,会对对方进行一次类加载,如果这两个类同时进行类初始化,就会出现死锁;
验证
验证代码:
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();
}
}
}