JDK8环境下fatjar打包的Java agent启动缓慢问题求助
问题根因
这个问题的核心是JDK 8的类加载锁机制和premain执行阶段的特性共同导致的:
- JDK 8为了兼容旧版本自定义类加载器,默认类加载使用全局层级锁:只要有一个参与类加载的类加载器没有显式实现
ParallelCapable标记接口,所有类加载操作都会抢占同一个全局锁,锁冲突概率极高。 - premain方法运行在JVM启动的主线程上,这个阶段刚好是JVM自身加载主程序依赖类的高峰,你在premain主线程同步执行agent的类加载逻辑时,会和JVM的类加载线程反复抢占全局类加载锁,导致每个类加载都要长时间等待锁释放,所以单类加载耗时飙升到数百毫秒。
你的测试结果也完全符合这个结论:
- 异步执行不阻塞主线程:主线程可以继续推进JVM自身的类加载流程,很快就度过了类加载高峰,后续子线程加载agent依赖时几乎没有锁竞争,所以速度正常。
- 阻塞主线程等异步任务执行完成:主线程卡在premain阶段,JVM自身的类加载无法推进,依然和子线程的类加载逻辑抢占锁,所以速度又变慢。
- JDK 11运行正常:JDK 9及之后版本默认所有内置类加载器都实现了
ParallelCapable接口,全局类加载锁被移除,锁粒度降低为「类加载器+类名」级别,几乎不会产生跨线程锁冲突,所以就算在premain阶段同步加载也不会有性能问题。
解决方案
方案一:异步初始化(推荐)
直接使用你测试通过的异步执行方案即可,成本最低。注意如果有必须在主程序类加载前完成的增强逻辑,可以加个简单的开关,在agent初始化完成前拦截需要增强的类,等初始化完成后再放行类加载即可。
方案二:开启类加载并行能力
如果必须在premain阶段同步完成agent初始化,可以给你用到的自定义类加载器显式实现ParallelCapable标记接口,关闭全局锁:
// 如果你自定义了类加载器,直接实现该接口即可 public class CustomAgentClassLoader extends ClassLoader implements ParallelCapable { // 类加载逻辑保持不变 }
如果你使用的Spring Boot版本自带的LaunchedURLClassLoader没有实现ParallelCapable,可以自定义类加载器继承LaunchedURLClassLoader并实现该接口,替换掉JarLauncher默认的类加载器即可。
验证方式
你可以在启动慢的时候执行jstack <pid>抓取线程栈,就能看到大量线程处于BLOCKED状态,阻塞在类加载的锁对象上,即可确认根因。
内容的提问来源于stack exchange,提问作者hammer
相关产品推荐
相关产品推荐

