類加載常見錯誤總結,寫得非常好!
點擊上方藍色“小哈學Java”,選擇“設為星標” 回復“資源”獲取獨家整理的學習資料!
作者:fredalxin
地址:https://fredal.xin/classloader-error
最近在做類隔離相關的一些工作,而恰恰之前協(xié)助開發(fā)同學時也發(fā)現(xiàn)會遇到許多類加載相關的異常,并且往往比較難定位與解決。這里簡單做一個小總結。
類加載
首先我們來捋一捋類加載的基礎知識。
以上是大家比較熟悉的類加載器模型,主要包含 3 種類加載器:
BootstrapClassloader 根加載器,也就是系統(tǒng)類加載器,加載核心庫,如 rt.jar。 ExtensionClassloader 擴展類加載器,主要加載/ext/下面的 jar 包 AppClassloader 離我們最近的類加載器,負責加載 classpath 下的類,開發(fā)時候我們的代碼大部分由其加載。
一個類是由 jvm 加載是通過類加載器+全限定類名確定唯一性的。 雙親委派,眾所周知,子加載器會盡量委托給父加載器進行加載,父加載器找不到再自己加載 線程上下文類加載,為了滿足 spi 等需求突破雙親委派機制,當高層類加載器想加載底層類時通過 Thread.contextClassLoader 來獲取當前線程的類加載器(往往是底層類加載器)去加載類。
ClassNotFoundException
ClassNotFoundException 表示類找不到異常,是一種 Exception,通常發(fā)生在載入階段,當開發(fā)者主動調用 Class.forName()、ClassLoader.loadClass()或 ClassLoader.findSystemClass()動態(tài)加載指定類時候,類加載器就會去 classpath 下尋找類,如果找不到就會拋出此錯誤。
還有另外一種情況是當一個類已經被某個類加載器加載到內存中,另外一個類加載器試圖去加載時也會發(fā)生錯誤。
ClassNotFoundException 是一個 exception 類,同時發(fā)生在主動執(zhí)行動態(tài)加載時,所以我們應該去 catch 它,防止發(fā)生一些運行時錯誤。
NoClassDefFoundError
NoClassDefFoundError 是一種和 ClassNotFoundException 很像的錯誤,只不過它是更嚴重的 error 類型。它發(fā)生在鏈接階段,表示 jvm 在編譯階段可以找到相應的類,但在執(zhí)行過程中卻找不到相應的類。
一種原因是由于在編譯后運行前類被更改或者刪除了。另外一種則是 classpath 本身被修改過了,這可以通過System.getProperty("java.classpath")來找到程序實際運行的 classpath,或者通過-classpath 命令來指定正確的 classpath。
那如果是在 ide 中開發(fā),很多時候出現(xiàn)的情況是我們可以通過 ide 編譯通過,但在實際運行的 WEB-INF/lib 下卻是沒有的。所以排查的時候我們需要去實際的 war 包下面確定是否有類。
NoSuchMethodError
我們還會遇到 NoSuchMethodError 錯誤,它表示找不到方法,但找不到方法歸根結底是找到了不正確的類。
這種情況我們首先得知道 jvm 到底加載的是什么版本,這可以使用-verbose:class來確定。
LinkageError
LinkageError 相比較之前幾種錯誤不那么常見,只有多個類加載器同時作用交互時才會出現(xiàn)。
我們知道 jvm 中一個類由全限定類名與類加載器確定類實例,那么不同類加載器加載的同一個類是屬于不同類實例的,然后在內存中如果兩者發(fā)生交互,就會出現(xiàn) LinkageError 異常。
一般情況下,jvm 加載類都會遵循之前所述的雙親委派原則,不太可能出現(xiàn)一個類有不同類加載器加載的情況。但在諸如 tomcat 之類的 javaEE 環(huán)境中,常常出這種狀況,這是由于 tomcat 上的 web 應用類加載機制稍有不同,每個資源模塊(比如一個 war 包)都優(yōu)先使用自身的資源,突破了雙親委派模型:

當 appClassLoader 加載類時候,會首先在自己的本地資源庫中查找類,其次才會走雙親委派模型。那么如果一個類 A 由 AppClassLoaderx 加載,但其超類在 AppClassLoader 中沒有,只有委托 CommonClassLoader 才能找到,當類 A 與其超類進行交互時就會報錯了。
還有一種比較常見的情況是進行自定義類加載器開發(fā)時遇到。比如開發(fā)類隔離容器時,期望將某些中間件都由與應用不同的獨立類加載器加載,但這時候如果中間件依賴 spring context,而應用本身也依賴 spring context,那么 作為 spring bean 交互時候就會妥妥報 LinkageError 了。
解決這個問題的辦法包括 2 種,即控制不同類加載器加載的類不進行交互,或者都交于一個共同的父加載器進行加載。
Some Tips
總結一下以上幾種錯誤。ClassNotFoundException 以及 NoClassDefFoundError 都是由于加載不到類導致的,而 NoSuchMethodError 是因為加載了不正確的類,LinkageError 則是由于同一個類被多個類加載器加載所導致的。
以上這些問題都可以使用arthas進行排查。例如使用 sc 命令來查看 JVM 已加載的類信息,包括從哪個 jar 包讀取,由哪個類加載器加載。使用 jad 命令來查看 jvm 中反編譯的代碼,可以定位到底到底有沒有所需 method。以及使用 classloader 命令,來查看當前所有 classloader 的信息,包括加載的 urls,是否能加載到指定的類或者 resources 等。
1. SpringBoot 實現(xiàn) MySQL 讀寫分離技術
3. Nginx 常用配置清單
最近面試BAT,整理一份面試資料《Java面試BATJ通關手冊》,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數(shù)據(jù)庫、數(shù)據(jù)結構等等。
獲取方式:點“在看”,關注公眾號并回復 Java 領取,更多內容陸續(xù)奉上。
文章有幫助的話,在看,轉發(fā)吧。
謝謝支持喲 (*^__^*)


