消失的死锁:从 JSF 线程池满到 JVM 初始化原理剖析 | 京东云技术团队

💡 原文中文,约10800字,阅读约需26分钟。
📝

内容提要

文章讲述了一次线上问题排查的过程,最终定位到代码存在并发锁,排查日志及业务代码后发现是初始化ProtoStuffSerializer这个类时失败,原因是存在jar包冲突和死锁。最终发现是类加载的问题,多个线程并发调用触发了这个类的多次初始化,只能让一个线程真正执行clinit方法,其他线程都必须等待。同时,文章也提醒大家要注意类初始化代码里产生循环依赖,以及jdk8的defalut特性也要谨慎。

🎯

关键要点

  • 上线后观察到只有一台机器出现jsf线程池耗尽的问题,迅速下线该机器的jsf接口。
  • 排查发现问题原因是代码存在并发锁,开始排查日志及业务代码。
  • 通过dump文件发现大量JSF线程处于RUNNABLE状态,问题初步定位到ProtoStuffSerializer类的初始化失败。
  • 排查过程中发现存在jar包冲突和死锁,但正常逻辑下死锁应该影响所有机器。
  • 最终确认是类加载的问题,多个线程并发调用导致ProtoStuffSerializer类的多次初始化。
  • 类的初始化过程涉及clinit方法,JVM保证该方法只能被一个线程执行,其他线程需等待。
  • 类加载的锁在jstack输出中不可见,导致死锁现象隐蔽。
  • 总结类加载的死锁现象,强调在类初始化代码中避免循环依赖,谨慎使用jdk8的default特性。
➡️

继续阅读