自定义ClassLoader实现为何引发线程间死锁?
类加载器交叉委托导致的死锁问题分析
问题背景
我有一个继承自URLClassLoader的类加载器LoaderX,实现了以下方法:
override fun loadClass(name: String, resolve: Boolean): Class<*> { try { return super.loadClass(name, resolve) } catch (_: ClassNotFoundException) { } return pluginClassLoaderManager.loadClass(name, this) } override fun loadClassNoParent(name: String): Class<*> { val lock = getClassLoadingLock(name) synchronized(lock) { return findLoadedClass(name) ?: findClass(name) } }
其中pluginClassLoaderManager是全局类加载器管理器,调用其loadClass方法会将加载请求委托给其他类加载器的loadClassNoParent方法。另外,URLClassLoader的getClassLoadingLock始终返回当前类加载器实例(未启用并行类加载)。
运行程序时出现死锁,线程栈信息如下:
"DefaultDispatcher-worker-3@2727" tid=0x24 nid=NA waiting for monitor entry java.lang.Thread.State: BLOCKED Blocked by DefaultDispatcher-worker-2@2726 Waiting for DefaultDispatcher-worker-2@2726 to release lock <0x157e> (x.PluginUrlClassLoader) at x.PluginUrlClassLoader.loadClassNoParent(PluginUrlClassLoader.kt:82) at x.PluginClassLoaderManager.loadClass(PluginClassLoaderManager.kt:73) at x.PluginUrlClassLoader.loadClass(PluginUrlClassLoader.kt:76) at java.lang.ClassLoader.loadClass(ClassLoader.java:526) "DefaultDispatcher-worker-2@2726" tid=0x23 nid=NA waiting for monitor entry java.lang.Thread.State: BLOCKED Blocked by DefaultDispatcher-worker-3@2727 Waiting for DefaultDispatcher-worker-3@2727 to release lock <0x157f> (x.PluginUrlClassLoader) at x.PluginUrlClassLoader.loadClassNoParent(PluginUrlClassLoader.kt:82) at xr.PluginClassLoaderManager.loadClass(PluginClassLoaderManager.kt:73) at x.PluginUrlClassLoader.loadClass(PluginUrlClassLoader.kt:76) at java.lang.ClassLoader.loadClass(ClassLoader.java:526)
我疑惑的是:从逻辑上看,线程获取锁后应该直接执行findClass并返回,为什么会出现互相等待锁的死锁情况?
死锁原因分析
死锁的核心是两个线程交叉持有对方需要的类加载器锁,形成循环等待,具体触发流程如下:
假设存在两个类加载器实例LoaderA(对应锁<0x157e>)和LoaderB(对应锁<0x157f>):
- 线程worker-2先获取了
LoaderA的锁,进入LoaderA.loadClassNoParent的同步块,在加载某个类时父类加载失败,于是委托给管理器;管理器将请求转发给LoaderB.loadClassNoParent,此时worker-2需要获取LoaderB的锁,但该锁已经被worker-3持有。 - 线程worker-3先获取了
LoaderB的锁,进入LoaderB.loadClassNoParent的同步块,加载另一个类时父类加载失败,委托给管理器;管理器将请求转发给LoaderA.loadClassNoParent,此时worker-3需要获取LoaderA的锁,但该锁已经被worker-2持有。
此时双方都持有一个锁,同时等待对方释放另一个锁,完全符合死锁的四个必要条件(互斥、持有并等待、不可剥夺、循环等待),因此陷入死锁。
你误以为死锁发生在findClass执行过程中,但实际上死锁发生在进入另一个类加载器的loadClassNoParent同步块的入口——线程还没来得及执行findClass,就已经卡在等待锁的状态了。
解决建议
- 避免交叉委托:调整类加载器的委托规则,确保类加载请求是单向依赖(比如LoaderA可以委托LoaderB,但LoaderB不能反向委托LoaderA),或者限制跨加载器委托的类范围。
- 全局锁控制:对跨加载器的类加载请求,使用一个全局统一的锁来同步,避免交叉等待(但会牺牲部分并发性能)。
- 循环检测:在管理器的
loadClass方法中,检测当前请求的委托链是否存在循环,若存在则抛出异常或跳过委托。
内容的提问来源于stack exchange,提问作者user27909701
相关产品推荐
相关产品推荐

