Java 15下Java agent抛出PreparedStatement NoClassDefFoundError
Java 15环境运行Java Agent抛出
NoClassDefFoundError: java.sql.PreparedStatement问题分析 java.lang.NoClassDefFoundError: java/sql/PreparedStatement at java.base/java.lang.Class.getDeclaredMethods0(Native Method) at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3325) at java.base/java.lang.Class.getDeclaredMethods(Class.java:2466) ... Caused by: java.lang.ClassNotFoundException: java.sql.PreparedStatement at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:435) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ... 19 more
核心成因
该异常的直接触发场景为:Java Agent通过反射扫描某类的声明方法时,JVM需要解析该类方法签名中引用的java.sql.PreparedStatement类型,此时当前类加载上下文无法加载对应类定义,就会抛出NoClassDefFoundError。结合Java 15的运行时规则,常见根因包括三类:
- JPMS模块加载缺失:Java 9开始JDK核心类按模块拆分,
java.sql.PreparedStatement隶属于java.sql模块,如果启动时通过--limit-modules参数限制了JVM加载的模块列表,未包含java.sql模块,就会导致该类完全无法被加载。 - 类加载委托逻辑异常:按照JDK默认的双亲委派规则,
java.*前缀的核心类应该由启动类加载器完成加载,URLClassLoader的findClass方法根本不应该参与这类核心类的查找。如果Java Agent自定义了独立的URLClassLoader实例,没有遵循双亲委派规则优先将JDK核心类的加载请求委托给上级类加载器,而是直接在自身的类路径下查找类,就会触发URLClassLoader查找JDK类的逻辑,最终抛出类不存在的错误。 - 模块访问权限不足:即使
java.sql模块正常加载,JPMS默认仅向未命名模块(类路径下的普通应用jar)开放模块的公共API访问权限,如果Agent做类反射解析时,所在的类加载上下文没有被授予java.sql模块的访问权限,就会触发类定义加载失败。
排查与解决方向
- 排查JVM启动参数,移除/修正非法的
--limit-modules配置,必要时添加--add-modules java.sql显式加载对应模块。 - 检查Agent的自定义类加载器实现,补全双亲委派委托逻辑,对
java.*前缀的核心类加载请求,直接委托给系统类加载器处理,不要在自定义类加载器路径下查找。 - 针对JPMS访问限制,添加启动参数
--add-opens java.sql/java.sql=ALL-UNNAMED,将java.sql模块下的类对所有未命名类加载上下文开放访问权限。 - 如果使用字节码增强框架实现Agent的插桩逻辑,确认类签名解析、反射操作的类加载上下文与被增强的业务类保持一致,避免在隔离的类加载器上下文中触发JDK类的加载流程。
内容的提问来源于stack exchange,提问作者Piyush Moolchandani
相关产品推荐
相关产品推荐

