解決java.lang.NoClassDefFoundError的坑

解決了線程池的問題後,突然冒出java.lang.NoClassDefFoundError的坑,本地運行時編譯正常且功能正常,查看環境上的pom文件和jar包,發現版本什麼的都與本地一致,類也存在,一時有點懵。查閱了資料之後,發現是打包時候未將jar包路徑加入到MANIFEST文件的classpath中。將路徑加進去之後問題解決。

這裏順便了解下這個坑的原因及解決辦法:

NoClassDefFoundError錯誤發生的原因

NoClassDefFoundError錯誤的發生,是因爲Java虛擬機在編譯時能找到合適的類,而在運行時不能找到合適的類導致的錯誤。例如在運行時我們想調用某個類的方法或者訪問這個類的靜態成員的時候,發現這個類不可用,此時Java虛擬機就會拋出NoClassDefFoundError錯誤。與ClassNotFoundException的不同在於,這個錯誤發生只在運行時需要加載對應的類不成功,而不是編譯時發生。很多Java開發者很容易在這裏把這兩個錯誤搞混。

簡單總結就是,NoClassDefFoundError發生在編譯時對應的類可用,而運行時在Java的classpath路徑中,對應的類不可用導致的錯誤。如果是主線程發生錯誤,程序將崩潰或停止,而如果是子線程,則子線程停止,其他線程繼續運行。

NoClassDefFoundError和ClassNotFoundException區別

我們經常被java.lang.ClassNotFoundException和java.lang.NoClassDefFoundError這兩個錯誤迷惑不清,儘管他們都與Java classpath有關,但是他們完全不同。NoClassDefFoundError發生在JVM在動態運行時,根據你提供的類名,在classpath中找到對應的類進行加載,但當它找不到這個類時,就發生了java.lang.NoClassDefFoundError的錯誤,而ClassNotFoundException是在編譯的時候在classpath中找不到對應的類而發生的錯誤。ClassNotFoundException比NoClassDefFoundError容易解決,是因爲在編譯時我們就知道錯誤發生,並且完全是由於環境的問題導致。而如果你在J2EE的環境下工作,並且得到NoClassDefFoundError的異常,而且對應的錯誤的類是確實存在的,這說明這個類對於類加載器來說,可能是不可見的。

怎麼解決NoClassDefFoundError錯誤

根據前文,很明顯NoClassDefFoundError的錯誤是因爲在運行時類加載器在classpath下找不到需要加載的類,所以我們需要把對應的類加載到classpath中,或者檢查爲什麼類在classpath中是不可用的,這個發生可能的原因如下:

  1. 對應的Class在java的classpath中不可用
  2. 你可能用jar命令運行你的程序,但類並沒有在jar文件的manifest文件中的classpath屬性中定義
  3. 可能程序的啓動腳本覆蓋了原來的classpath環境變量
  4. 因爲NoClassDefFoundError是java.lang.LinkageError的一個子類,所以可能由於程序依賴的原生的類庫不可用而導致
  5. 檢查日誌文件中是否有java.lang.ExceptionInInitializerError這樣的錯誤,NoClassDefFoundError有可能是由於靜態初始化失敗導致的
  6. 如果你工作在J2EE的環境,有多個不同的類加載器,也可能導致NoClassDefFoundError

NoClassDefFoundError解決示例

  • 當發生由於缺少jar文件,或者jar文件沒有添加到classpath,或者jar的文件名發生變更會導致java.lang.NoClassDefFoundError的錯誤。
  • 當類不在classpath中時,這種情況很難確切的知道,但如果在程序中打印出System.getproperty(“java.classpath”),可以得到程序實際運行的classpath
  • 運行時明確指定你認爲程序能正常運行的 -classpath 參數,如果增加之後程序能正常運行,說明原來程序的classpath被其他人覆蓋了。
  • NoClassDefFoundError也可能由於類的靜態初始化模塊錯誤導致,當你的類執行一些靜態初始化模塊操作,如果初始化模塊拋出異常,哪些依賴這個類的其他類會拋出NoClassDefFoundError的錯誤。如果你查看程序日誌,會發現一些java.lang.ExceptionInInitializerError的錯誤日誌,ExceptionInInitializerError的錯誤會導致java.lang.NoClassDefFoundError: Could not initialize class,
  • 由於NoClassDefFoundError是LinkageError的子類,而LinkageError的錯誤在依賴其他的類時會發生,所以如果你的程序依賴原生的類庫和需要的dll不存在時,有可能出現java.lang.NoClassDefFoundError。這種錯誤也可能拋出java.lang.UnsatisfiedLinkError: no dll in java.library.path Exception Java這樣的異常。解決的辦法是把依賴的類庫和dll跟你的jar包放在一起。
  • 如果你使用Ant構建腳本來生成jar文件和manifest文件,要確保Ant腳本獲取的是正確的classpath值寫入到manifest.mf文件
  • Jar文件的權限問題也可能導致NoClassDefFoundError,如果你的程序運行在像linux這樣多用戶的操作系統種,你需要把你應用相關的資源文件,如Jar文件,類庫文件,配置文件的權限單獨分配給程序所屬用戶組,如果你使用了多個用戶不同程序共享的jar包時,很容易出現權限問題。比如其他用戶應用所屬權限的jar包你的程序沒有權限訪問,會導致java.lang.NoClassDefFoundError的錯誤。
  • 基於XML配置的程序也可能導致NoClassDefFoundError的錯誤。比如大多數Java的框架像Spring,Struts使用xml配置獲取對應的bean信息,如果你輸入了錯誤的名稱,程序可能會加載其他錯誤的類而導致NoClassDefFoundError異常。我們在使用Spring MVC框架或者Apache Struts框架,在部署War文件或者EAR文件時就經常會出現Exception in thread “main” java.lang.NoClassDefFoundError。
  • 在有多個ClassLoader的J2EE的環境中,很容易出現NoClassDefFoundError的錯誤。由於J2EE沒有指明標準的類加載器,使用的類加載器依賴與不同的容器像Tomcat、WebLogic,WebSphere加載J2EE的不同組件如War包或者EJB-JAR包。

    總結來說,類加載器基於三個機制:委託、可見性和單一性,委託機制是指將加載一個類的請求交給父類加載器,如果這個父類加載器不能夠找到或者加載這個類,那麼再加載它。可見性的原理是子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類。單一性原理是指僅加載一個類一次,這是由委託機制確保子類加載器不會再次加載父類加載器加載過的類。現在假設一個User類在WAR文件和EJB-JAR文件都存在,並且被WAR ClassLoader加載,而WAR ClassLoader是加載EJB-JAR ClassLoader的子ClassLoader。當EJB-JAR中代碼引用這個User類時,加載EJB-JAR所有class的Classloader找不到這個類,因爲這個類已經被EJB-JAR classloader的子加載器WAR classloader加載。

    這會導致的結果就是對User類出現NoClassDefFoundError異常,而如果在兩個JAR包中這個User類都存在,如果你使用equals方法比較兩個類的對象時,會出現ClassCastException的異常,因爲兩個不同類加載器加載的類無法進行比較。

  • 有時候會出現Exception in thread “main” java.lang.NoClassDefFoundError: com/sun/tools/javac/Main 這樣的錯誤,這個錯誤說明你的Classpath, PATH 或者 JAVA_HOME沒有安裝配置正確或者JDK的安裝不正確。這個問題的解決辦法時重新安裝你的JDK。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章