天天看點

深入JVM分析spring-boot應用hibernate-validator

這個錯誤資訊表面上是<code>NoClassDefFoundError</code>,但是實際上<code>ConfigurationImpl</code>這個類是在<code>hibernate-validator-5.3.5.Final.jar</code>裡的,不應該出現找不到類的情況。

那為什麼應用裡抛出這個<code>NoClassDefFoundError</code> ?

有經驗的開發人員從<code>Could not initialize class</code> 這個資訊就可以知道,實際上是一個類在初始化時抛出的異常,比如static的靜态代碼塊,或者static字段初始化的異常。

但是當我們在<code>HibernateValidator</code> 這個類,建立<code>ConfigurationImpl</code>的代碼塊裡打斷點時,發現有兩個線程觸發了斷點:

其中一個線程的調用棧是:

另外一個線程調用棧是:

顯然,這個線程的調用棧是常見的spring的初始化過程。

那麼重點來看下 <code>BackgroundPreinitializer</code> 線程做了哪些事情:

可以看到<code>BackgroundPreinitializer</code>類是spring boot為了加速應用的初始化,以一個獨立的線程來加載hibernate validator這些元件。

這個 <code>background-preinit</code> 線程會吞掉所有的異常。

顯然<code>ConfigurationImpl</code> 初始化的異常也被吞掉了,那麼如何才能擷取到最原始的資訊?

在<code>BackgroundPreinitializer</code>的 <code>run()</code> 函數裡打一個斷點(注意是<code>Suspend thread</code>類型, 不是<code>Suspend VM</code>),讓它先不要觸發<code>ConfigurationImpl</code>的加載,讓spring boot的正常流程去觸發<code>ConfigurationImpl</code>的加載,就可以知道具體的資訊了。

那麼打出來的異常資訊是:

那麼可以看出是 <code>org.jboss.logging.Logger</code> 這個類不相容,少了<code>getMessageLogger(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Object</code> 這個函數。

那麼檢查下應用的依賴,可以發現<code>org.jboss.logging.Logger</code> 在<code>jboss-common-1.2.1.GA.jar</code>和<code>jboss-logging-3.3.1.Final.jar</code>裡都有。

顯然是<code>jboss-common-1.2.1.GA.jar</code> 這個依賴過時了,需要排除掉。

應用依賴了<code>jboss-common-1.2.1.GA.jar</code>,它裡面的<code>org.jboss.logging.Logger</code>太老

spring boot啟動時,<code>BackgroundPreinitializer</code>裡的線程去嘗試加載<code>ConfigurationImpl</code>,然後觸發了<code>org.jboss.logging.Logger</code>的函數執行問題

<code>BackgroundPreinitializer</code> 吃掉了異常資訊,jvm把<code>ConfigurationImpl</code>标記為不可用的

spring boot正常的流程去加載<code>ConfigurationImpl</code>,jvm發現<code>ConfigurationImpl</code>類是不可用,直接抛出<code>NoClassDefFoundError</code>

“` 

““

為什麼第二次嘗試加載<code>ConfigurationImpl</code>時,會直接抛出<code>java.lang.NoClassDefFoundError: Could not initialize class</code> ?

下面用一段簡單的代碼來重制這個問題:

當抛出第一個異常時,嘗試用HSDB來看下這個類的狀态。

然後在HSDB console裡查找到<code>Version</code>的位址資訊

然後在<code>Inspector</code>查找到這個位址,發現<code>_init_state</code>是5。

深入JVM分析spring-boot應用hibernate-validator

再看下hotspot代碼,可以發現5對應的定義是<code>initialization_error</code>:

<a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5" target="_blank">http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5</a>

從規範裡可以看到初始一個類/接口有12步,比較重要的兩步都用黑體标記出來了:

If the Class object for C is in an erroneous state, then initialization is not possible. Release LC and throw a NoClassDefFoundError.

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.

當第一次嘗試加載時,hotspot InterpreterRuntime在解析<code>invokestatic</code>指令時,嘗試加載<code>org.hibernate.validator.internal.util.Version</code>類,<code>InstanceKlass</code>的<code>_init_state</code>先是标記為<code>being_initialized</code>,然後當加載失敗時,被标記為<code>initialization_error</code>。

對應<code>Initialization</code>的11步。

當第二次嘗試加載時,檢查<code>InstanceKlass</code>的<code>_init_state</code>是<code>initialization_error</code>,則直接抛出<code>NoClassDefFoundError: Could not initialize class</code>.

對應<code>Initialization</code>的5步。

spring boot在<code>BackgroundPreinitializer</code>類裡用一個獨立的線程來加載validator,并吃掉了原始異常

第一次加載失敗的類,在jvm裡會被标記為<code>initialization_error</code>,再次加載時會直接抛出<code>NoClassDefFoundError: Could not initialize class</code>

當在代碼裡吞掉異常時要謹慎,否則排查問題帶來很大的困難

http://blog.csdn.net/hengyunabc/article/details/71513509