百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

从 JVM 分析 hibernate-validator NoClassDefFoundError

toyiye 2024-06-21 12:18 9 浏览 0 评论

最近排查一个spring boot应用抛出hibernate.validator NoClassDefFoundError的问题,异常信息如下:

Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.hibernate.validator.internal.engine.ConfigurationImpl
 at org.hibernate.validator.HibernateValidator.createGenericConfiguration(HibernateValidator.java:33) ~[hibernate-validator-5.3.5.Final.jar:5.3.5.Final]
 at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:276) ~[validation-api-1.1.0.Final.jar:na]
 at org.springframework.boot.validation.MessageInterpolatorFactory.getObject(MessageInterpolatorFactory.java:53) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
 at org.springframework.boot.autoconfigure.validation.DefaultValidatorConfiguration.defaultValidator(DefaultValidatorConfiguration.java:43) ~[spring-boot-autoconfigure-1.5.3.RELEASE.jar:1.5.3.RELEASE]
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112]
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_112]
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_112]
 at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_112]
 at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162) ~[spring-beans-4.3.8.RELEASE.jar:4.3.8.RELEASE]
 ... 32 common frames omitted

这个错误信息表面上是 NoClassDefFoundError ,但是实际上 ConfigurationImpl 这个类是在 hibernate-validator-5.3.5.Final.jar 里的,不应该出现找不到类的情况。

那为什么应用里抛出这个 NoClassDefFoundError ?

有经验的开发人员从 Could not initialize class 这个信息就可以知道,实际上是一个类在初始化时抛出的异常,比如static的静态代码块,或者static字段初始化的异常。

谁初始化了 org.hibernate.validator.internal.engine.ConfigurationImpl

但是当我们在 HibernateValidator 这个类,创建 ConfigurationImpl 的代码块里打断点时,发现有两个线程触发了断点:

public class HibernateValidator implements ValidationProvider<HibernateValidatorConfiguration> {
 @Override
 public Configuration<?> createGenericConfiguration(BootstrapState state) {
 return new ConfigurationImpl( state );
 }

其中一个线程的调用栈是:

Thread [background-preinit] (Class load: ConfigurationImpl)
 HibernateValidator.createGenericConfiguration(BootstrapState) line: 33
 Validation$GenericBootstrapImpl.configure() line: 276
 BackgroundPreinitializer$ValidationInitializer.run() line: 107
 BackgroundPreinitializer$1.runSafely(Runnable) line: 59
 BackgroundPreinitializer$1.run() line: 52
 Thread.run() line: 745

另外一个线程调用栈是:

Thread [main] (Suspended (breakpoint at line 33 in HibernateValidator))
 owns: ConcurrentHashMap<K,V> (id=52)
 owns: Object (id=53)
 HibernateValidator.createGenericConfiguration(BootstrapState) line: 33
 Validation$GenericBootstrapImpl.configure() line: 276
 MessageInterpolatorFactory.getObject() line: 53
 DefaultValidatorConfiguration.defaultValidator() line: 43
 NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
 NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
 DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
 Method.invoke(Object, Object...) line: 498
 CglibSubclassingInstantiationStrategy(SimpleInstantiationStrategy).instantiate(RootBeanDefinition, String, BeanFactory, Object, Method, Object...) line: 162
 ConstructorResolver.instantiateUsingFactoryMethod(String, RootBeanDefinition, Object[]) line: 588
 DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).instantiateUsingFactoryMethod(String, RootBeanDefinition, Object[]) line: 1173

显然,这个线程的调用栈是常见的spring的初始化过程。

BackgroundPreinitializer 做了什么

那么重点来看下 BackgroundPreinitializer 线程做了哪些事情:

@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer
 implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
 @Override
 public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
 try {
 Thread thread = new Thread(new Runnable() {
 @Override
 public void run() {
 runSafely(new MessageConverterInitializer());
 runSafely(new MBeanFactoryInitializer());
 runSafely(new ValidationInitializer());
 runSafely(new JacksonInitializer());
 runSafely(new ConversionServiceInitializer());
 }
 public void runSafely(Runnable runnable) {
 try {
 runnable.run();
 }
 catch (Throwable ex) {
 // Ignore
 }
 }
 }, "background-preinit");
 thread.start();
 }

可以看到 BackgroundPreinitializer 类是spring boot为了加速应用的初始化,以一个独立的线程来加载hibernate validator这些组件。

这个 background-preinit 线程会吞掉所有的异常。

显然 ConfigurationImpl 初始化的异常也被吞掉了,那么如何才能获取到最原始的信息?

获取到最原始的异常信息

在 BackgroundPreinitializer 的 run() 函数里打一个断点(注意是 Suspend thread 类型, 不是 Suspend VM ),让它先不要触发 ConfigurationImpl 的加载,让spring boot的正常流程去触发 ConfigurationImpl 的加载,就可以知道具体的信息了。

那么打出来的异常信息是:

Caused by: java.lang.NoSuchMethodError: org.jboss.logging.Logger.getMessageLogger(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Object;
 at org.hibernate.validator.internal.util.logging.LoggerFactory.make(LoggerFactory.java:19) ~[hibernate-validator-5.3.5.Final.jar:5.3.5.Final]
 at org.hibernate.validator.internal.util.Version.<clinit>(Version.java:22) ~[hibernate-validator-5.3.5.Final.jar:5.3.5.Final]
 at org.hibernate.validator.internal.engine.ConfigurationImpl.<clinit>(ConfigurationImpl.java:71) ~[hibernate-validator-5.3.5.Final.jar:5.3.5.Final]
 at org.hibernate.validator.HibernateValidator.createGenericConfiguration(HibernateValidator.java:33) ~[hibernate-validator-5.3.5.Final.jar:5.3.5.Final]
 at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:276) ~[validation-api-1.1.0.Final.jar:na]
 at org.springframework.boot.validation.MessageInterpolatorFactory.getObject(MessageInterpolatorFactory.java:53) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]

那么可以看出是 org.jboss.logging.Logger 这个类不兼容,少了 getMessageLogger(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Object 这个函数。

那么检查下应用的依赖,可以发现 org.jboss.logging.Logger 在 jboss-common-1.2.1.GA.jar和 jboss-logging-3.3.1.Final.jar 里都有。

显然是 jboss-common-1.2.1.GA.jar 这个依赖过时了,需要排除掉。

总结异常的发生流程

  1. 应用依赖了 jboss-common-1.2.1.GA.jar ,它里面的 org.jboss.logging.Logger 太老
  2. spring boot启动时, BackgroundPreinitializer 里的线程去尝试加载 ConfigurationImpl ,然后触发了 org.jboss.logging.Logger 的函数执行问题
  3. BackgroundPreinitializer 吃掉了异常信息,jvm把 ConfigurationImpl 标记为不可用的
  4. spring boot正常的流程去加载 ConfigurationImpl ,jvm发现 ConfigurationImpl 类是不可用,直接抛出 NoClassDefFoundError
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.hibernate.validator.internal.engine.ConfigurationImpl

深入JVM

为什么第二次尝试加载 ConfigurationImpl 时,会直接抛出 java.lang.NoClassDefFoundError: Could not initialize class ?

下面用一段简单的代码来重现这个问题:

try {
 org.hibernate.validator.internal.util.Version.touch();
} catch (Throwable e) {
 e.printStackTrace();
}
System.in.read();
try {
 org.hibernate.validator.internal.util.Version.touch();
} catch (Throwable e) {
 e.printStackTrace();
}

使用HSDB来确定类的状态

当抛出第一个异常时,尝试用HSDB来看下这个类的状态。

sudo java -classpath "$JAVA_HOME/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB

然后在HSDB console里查找到 Version 的地址信息

hsdb> class org.hibernate.validator.internal.util.Version
org/hibernate/validator/internal/util/Version @0x00000007c0060218

然后在 Inspector 查找到这个地址,发现 _init_state 是5。

再看下hotspot代码,可以发现5对应的定义是 initialization_error :

// /hotspot/src/share/vm/oops/instanceKlass.hpp
// See "The Java Virtual Machine Specification" section 2.16.2-5 for a detailed description
// of the class loading & initialization procedure, and the use of the states.
enum ClassState {
 allocated, // allocated (but not yet linked)
 loaded, // loaded and inserted in class hierarchy (but not linked yet)
 linked, // successfully linked/verified (but not initialized yet)
 being_initialized, // currently running class initializer
 fully_initialized, // initialized (successfull final state)
 initialization_error // error happened during initialization
};

JVM规范里关于Initialization的内容

http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5

从规范里可以看到初始一个类/接口有12步,比较重要的两步都用黑体标记出来了:

  • 5: If the Class object for C is in an erroneous state, then initialization is not possible. Release LC and throw a NoClassDefFoundError.
  • 11: Otherwise, the class or interface initialization method must have completed abruptly by throwing some exception E. If the class of E is not Error or one of its subclasses, then create a new instance of the class ExceptionInInitializerError with E as the argument, and use this object in place of E in the following step.

第一次尝试加载Version类时

当第一次尝试加载时,hotspot InterpreterRuntime在解析 invokestatic 指令时,尝试加载 org.hibernate.validator.internal.util.Version 类, InstanceKlass 的 _init_state 先是标记为 being_initialized ,然后当加载失败时,被标记为 initialization_error 。

对应 Initialization 的11步。

// hotspot/src/share/vm/oops/instanceKlass.cpp
// Step 10 and 11
Handle e(THREAD, PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
// JVMTI has already reported the pending exception
// JVMTI internal flag reset is needed in order to report ExceptionInInitializerError
JvmtiExport::clear_detected_exception((JavaThread*)THREAD);
{
 EXCEPTION_MARK;
 this_oop->set_initialization_state_and_notify(initialization_error, THREAD);
 CLEAR_PENDING_EXCEPTION; // ignore any exception thrown, class initialization error is thrown below
 // JVMTI has already reported the pending exception
 // JVMTI internal flag reset is needed in order to report ExceptionInInitializerError
 JvmtiExport::clear_detected_exception((JavaThread*)THREAD);
}
DTRACE_CLASSINIT_PROBE_WAIT(error, InstanceKlass::cast(this_oop()), -1,wait);
if (e->is_a(SystemDictionary::Error_klass())) {
 THROW_OOP(e());
} else {
 JavaCallArguments args(e);
 THROW_ARG(vmSymbols::java_lang_ExceptionInInitializerError(),
 vmSymbols::throwable_void_signature(),
 &args);
}

第二次尝试加载Version类时

当第二次尝试加载时,检查 InstanceKlass 的 _init_state 是 initialization_error ,则直接抛出 NoClassDefFoundError: Could not initialize class .

对应 Initialization 的5步。

// hotspot/src/share/vm/oops/instanceKlass.cpp
void InstanceKlass::initialize_impl(instanceKlassHandle this_oop, TRAPS) {
// ...
 // Step 5
 if (this_oop->is_in_error_state()) {
 DTRACE_CLASSINIT_PROBE_WAIT(erroneous, InstanceKlass::cast(this_oop()), -1,wait);
 ResourceMark rm(THREAD);
 const char* desc = "Could not initialize class ";
 const char* className = this_oop->external_name();
 size_t msglen = strlen(desc) + strlen(className) + 1;
 char* message = NEW_RESOURCE_ARRAY(char, msglen);
 if (NULL == message) {
 // Out of memory: can't create detailed error message
 THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
 } else {
 jio_snprintf(message, msglen, "%s%s", desc, className);
 THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
 }
 }

总结

  • spring boot在 BackgroundPreinitializer 类里用一个独立的线程来加载validator,并吃掉了原始异常
  • 第一次加载失败的类,在jvm里会被标记为 initialization_error ,再次加载时会直接抛出 NoClassDefFoundError: Could not initialize class
  • 当在代码里吞掉异常时要谨慎,否则排查问题带来很大的困难

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码