wiflish
          Loving Life! Loving Coding!
          posts - 98,comments - 98,trackbacks - 0

          原文鏈接:http://www-128.ibm.com/developerworks/cn/java/j-dclp2.html


          ClassNotFoundException

          ClassNotFoundException 是最常見的類裝入異常類型。它發(fā)生在裝入階段。Java 規(guī)范對 ClassNotFoundException 的描述是這樣的:

          當應用程序試圖通過類的字符串名稱,使用以下三種方法裝入類,但卻找不到指定名稱的類定義時拋出該異常。

          • Class 中的 forName() 方法。
          • ClassLoader 中的 findSystemClass() 方法。
          • ClassLoader 中的 loadClass() 方法。

          所以,如果顯式地裝入類的嘗試失敗,那么就拋出 ClassNotFoundException。清單 1 中的測試用例提供的示例代碼拋出了一個 ClassNotFoundException


          清單 1. ClassNotFoundExceptionTest.java
          												
          														

          import java.net.MalformedURLException;
          import java.net.URL;
          import java.net.URLClassLoader;

          public class ClassNotFoundExceptionTest {

          public static void main(String args[]) {
          try {
          URLClassLoader loader = new URLClassLoader(new URL[] { new URL(
          "file://C:/CL_Article/ClassNotFoundException/")});
          loader.loadClass("DoesNotExist");
          } catch (ClassNotFoundException e) {
          e.printStackTrace();
          } catch (MalformedURLException e) {
          e.printStackTrace();
          }
          }
          }

          這個測試用例定義了一個類裝入器(MyClassLoader),用于裝入一個不存在的類(DoesNotExist)。當它運行時,會出現(xiàn)以下異常:

          												
          														
          java.lang.ClassNotFoundException: DoesNotExist
          at java.net.URLClassLoader.findClass(URLClassLoader.java:376)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:572)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
          at ClassNotFoundExceptionTest.main(ClassNotFoundExceptionTest.java:11)

          因為這個測試試圖使用對 loadClass() 的顯式調(diào)用來進行裝入,所以拋出 ClassNotFoundException

          通過拋出 ClassNotFoundException,類裝入器提示,定義類時所需要的字節(jié)碼在類裝入器所查找的位置上不存在。這些異常修復起來通常比較簡單。可以用 IBM 的 verbose 選項檢查類路徑,確保使用的類路徑設置正確(要獲得 verbose 的更多信息,請參閱本系列的 第一篇文章)。如果類路徑設置正確,但是仍然看到這個錯誤,那么就是需要的類在類路徑中不存在。要修復這個問題,可以把類移動到類路徑中指定的目錄或 JAR 文件中,或者把類所在的位置添加到類路徑中。







          NoClassDefFoundError

          NoClassDefFoundError 是類裝入器在裝入階段拋出的另一個常見異常。JVM 規(guī)范對 NoClassDefFoundError 的定義如下:

          如果 Java 虛擬機或 ClassLoader 實例試圖裝入類定義(作為正常的方法調(diào)用的一部分,或者作為使用 new 表達式創(chuàng)建新實例的一部分),但卻沒有找到類定義時拋出該異常。

          當目前執(zhí)行的類已經(jīng)編譯,但是找不到它的定義時,會存在 searched-for 類定義。

          實際上,這意味著 NoClassDefFoundError 的拋出,是不成功的隱式類裝入的結(jié)果。

          清單 2 到清單 4 的測試用例產(chǎn)生了 NoClassDefFoundError,因為類 B 的隱式裝入會失敗:


          清單 2. NoClassDefFoundErrorTest.java
          												
          														

          public class NoClassDefFoundErrorTest {
          public static void main(String[] args) {
          A a = new A();
          }
          }


          清單 3. A.java
          												
          														

          public class A extends B {
          }


          清單 4. B.java
          												
          														

          public class B {
          }

          這幾個清單中的代碼編譯好之后,刪除 B 的類文件。當代碼執(zhí)行時,就會出現(xiàn)以下錯誤:

          												
          														
          Exception in thread "main" java.lang.NoClassDefFoundError: B
          at java.lang.ClassLoader.defineClass0(Native Method)
          at java.lang.ClassLoader.defineClass(ClassLoader.java:810)
          at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:147)
          at java.net.URLClassLoader.defineClass(URLClassLoader.java:475)
          at java.net.URLClassLoader.access$500(URLClassLoader.java:109)
          at java.net.URLClassLoader$ClassFinder.run(URLClassLoader.java:848)
          at java.security.AccessController.doPrivileged1(Native Method)
          at java.security.AccessController.doPrivileged(AccessController.java:389)
          at java.net.URLClassLoader.findClass(URLClassLoader.java:371)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:572)
          at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:442)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
          at NoClassDefFoundErrorTest.main(NoClassDefFoundErrorTest.java:3)

          A 擴展了類 B;所以,當類 A 裝入時,類裝入器會隱式地裝入類 B。因為類 B 不存在,所以拋出 NoClassDefFoundError。如果顯式地告訴類裝入器裝入類 B(例如通過 loadClass("B") 調(diào)用),那么就會拋出 ClassNotFoundException

          顯然,要修復這個特殊示例中的問題,在對應的類裝入器的類路徑中,必須存在類 B。這個示例看起來可能價值不大、也不真實,但是,在復雜的有許多類的真實系統(tǒng)中,會因為類在打包或部署期間的遺失而發(fā)生這類情況。

          在這個例子中,A 擴展了 B;但是,即使 A 用其他方式引用 B,也會出現(xiàn)同樣的問題 —— 例如,以方法參數(shù)引用或作為實例字段。如果兩個類之間的關(guān)系是引用關(guān)系而不是繼承關(guān)系,那么會在第一次使用 A 時拋出錯誤,而不是在裝入 A 時拋出。







          ClassCastException

          類裝入器能夠拋出的另一個異常是 ClassCastException。它是在類型比較中發(fā)現(xiàn)不兼容類型的時候拋出的。JVM 規(guī)范指定 ClassCastException 是:

          該異常的拋出,表明代碼企圖把對象的類型轉(zhuǎn)換成一個子類,而該對象并不是這個子類的實例。

          清單 5 演示的代碼示例會產(chǎn)生一個 ClassCastException


          清單 5. ClassCastException.java
          												
          														

          public class ClassCastExceptionTest {
          public ClassCastExceptionTest() {
          }

          private static void storeItem(Integer[] a, int i, Object item) {
          a[i] = (Integer) item;
          }

          public static void main(String args[]) {
          Integer[] a = new Integer[3];
          try {
          storeItem(a, 2, new String("abc"));
          } catch (ClassCastException e) {
          e.printStackTrace();
          }
          }
          }

          在清單 5 中,調(diào)用了 storeItem() 方法,使用一個 Integer 數(shù)組、一個 int 和一個字符串作為參數(shù)。但是在內(nèi)部,該方法做了兩件事:

          • 隱式地把 String 對象類型轉(zhuǎn)換成 Object 類型(用于參數(shù)列表)。
          • 顯式地把這個 Object 類型轉(zhuǎn)換成 Integer 類型(在方法定義中)。

          當程序運行時,會出現(xiàn)以下異常:

          												
          														
          java.lang.ClassCastException: java.lang.String
          at ClassCastExceptionTest.storeItem(ClassCastExceptionTest.java:6)
          at ClassCastExceptionTest.main(ClassCastExceptionTest.java:12)

          這個異常是由顯式類型轉(zhuǎn)換拋出的,因為測試用例試圖把類型為 String 的東西轉(zhuǎn)換成 Integer

          當檢查對象(例如清單 5 中的 item)并把類型轉(zhuǎn)換成目標類(Integer)時,類裝入器會檢查以下規(guī)則:

          • 對于普通對象(非數(shù)組):對象必須是目標類的實例或目標類的子類的實例。如果目標類是接口,那么會把它當作實現(xiàn)了該接口的一個子類。

          • 對于數(shù)組類型:目標類必須是數(shù)組類型或 java.lang.Objectjava.lang.Cloneablejava.io.Serializable

          如果違反了以上任何一條規(guī)則,那么類裝入器就會拋出 ClassCastException。修復這類異常的最簡單方式就是仔細檢查對象要轉(zhuǎn)換到的類型是否符合以上提到的規(guī)則。在某些情況下,在做類型轉(zhuǎn)換之前用 instanceof 進行檢查是有意義的。







          UnsatisfiedLinkError

          在把本機調(diào)用鏈接到對應的本機定義時,類裝入器扮演著重要角色。如果程序試圖裝入一個不存在或者放錯的本機庫時,在鏈接階段的解析過程會發(fā)生 UnsatisfiedLinkError。JVM 規(guī)范指定 UnsatisfiedLinkError 是:

          對于聲明為 native 的方法,如果 Java 虛擬機找不到和它對應的本機語言定義,就會拋出該異常。

          當調(diào)用本機方法時,類裝入器會嘗試裝入定義了該方法的本機庫。如果找不到這個庫,就會拋出這個錯誤。

          清單 6 演示了拋出 UnsatisfiedLinkError 的測試用例 :


          清單 6. UnsatisfiedLinkError.java
          												
          														

          public class UnsatisfiedLinkErrorTest {

          public native void call_A_Native_Method();

          static {
          System.loadLibrary("myNativeLibrary");
          }

          public static void main(String[] args) {
          new UnsatisfiedLinkErrorTest().call_A_Native_Method();
          }
          }

          這段代碼調(diào)用本機方法 call_A_Native_Method(),該方法是在本機庫 myNativeLibrary 中定義的。因為這個庫不存在,所以在程序運行時會發(fā)生以下錯誤:

          												
          														
          The java class could not be loaded. java.lang.UnsatisfiedLinkError:
          Can't find library myNativeLibrary (myNativeLibrary.dll)
          in sun.boot.library.path or java.library.path
          sun.boot.library.path=D:\sdk\jre\bin
          java.library.path= D:\sdk\jre\bin

          at java.lang.ClassLoader$NativeLibrary.load(Native Method)
          at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2147)
          at java.lang.ClassLoader.loadLibrary(ClassLoader.java:2006)
          at java.lang.Runtime.loadLibrary0(Runtime.java:824)
          at java.lang.System.loadLibrary(System.java:908)
          at UnsatisfiedLinkErrorTest.<clinit>(UnsatisfiedLinkErrorTest.java:6)

          本機庫的裝入由調(diào)用 System.loadLibrary() 方法的類的類裝入器啟動 —— 在清單 6 中,就是 UnsatisfiedLinkErrorTest 的類裝入器。根據(jù)使用的類裝入器,會搜索不同的位置:

          • 對于由 bootstrap 類裝入器裝入的類,搜索 sun.boot.library.path
          • 對于由擴展類裝入器裝入的類,先搜索 java.ext.dirs,然后是 sun.boot.library.path,然后是 java.library.path
          • 對于由系統(tǒng)類裝入器裝入的類,搜索 sun.boot.library.path,然后是 java.library.path

          在清單 6 中,UnsatisfiedLinkErrorTest 類是由系統(tǒng)類裝入器裝入的。要裝入所引用的本機庫,這個類裝入器先查找 sun.boot.library.path,然后查找 java.library.path。因為在兩個位置中都沒有需要的庫,所以類裝入器拋出 UnsatisfiedLinkageError

          一旦理解了庫裝入過程所涉及的類裝入器,就可以通過把庫放在合適位置來解決這類問題。







          ClassCircularityError

          JVM 規(guī)范指定 ClassCircularityError 的拋出條件是:

          類或接口由于是自己的超類或超接口而不能被裝入。

          這個錯誤是在鏈接階段的解析過程中拋出的。這個錯誤有點奇怪,因為 Java 編譯器不允許發(fā)生這種循環(huán)情況。但是,如果獨立地編譯類,然后再把它們放在一起,就可能發(fā)生這個錯誤。請設想以下場景。首先,編譯清單 7 和清單 8 中的類:


          清單 7. A.java
          												
          														

          public class A extends B {
          }


          清單 8. B.java
          												
          														

          public class B {
          }

          然后,分別編譯清單 9 和清單 10 中的類:


          清單 9. A.java
          												
          														

          public class A {
          }


          清單 10. B.java
          												
          														

          public class B extends A {
          }

          最后,采用清單 7 的類 A 和清單 10 的類 B,并運行一個應用程序,試圖裝入 A 或者 B。這個情況看起來可能不太可能,但是在復雜的系統(tǒng)中,在把不同部分放在一起的時候,可能會發(fā)生類似的情況。

          顯然,要修復這個問題,必須避免循環(huán)的類層次結(jié)構(gòu)。







          ClassFormatError

          JVM 規(guī)范指出,拋出 ClassFormatError 的條件是:

          負責指定所請求的編譯類或接口的二進制數(shù)據(jù)形式有誤。

          這個異常是在類裝入的鏈接階段的校驗過程中拋出。如果字節(jié)碼發(fā)生了更改,例如主版本號或次版本號發(fā)生了更改,那么二進制數(shù)據(jù)的形式就會有誤。例如,如果對字節(jié)碼故意做了更改,或者在通過網(wǎng)絡傳送類文件時現(xiàn)出了錯誤,那么就可能發(fā)生這個異常。

          修復這個問題的惟一方法就是獲得字節(jié)碼的正確副本,可能需要重新進行編譯。







          ExceptionInInitializerError

          根據(jù) JVM 規(guī)范,拋出 ExceptionInInitializer 的情況是:

          • 如果初始化器突然完成,拋出一些異常 E,而且 E 的類不是 Error 或者它的某個子類,那么就會創(chuàng)建 ExceptionInInitializerError 類的一個新實例,并用 E 作為參數(shù),用這個實例代替 E

          • 如果 Java 虛擬機試圖創(chuàng)建類 ExceptionInInitializerError 的新實例,但是因為出現(xiàn) Out-Of-Memory-Error 而無法創(chuàng)建新實例,那么就拋出 OutOfMemoryError 對象作為代替。

          清單 8 中的代碼拋出 ExceptionInInitializerError


          清單 8. ExceptionInInitializerErrorTest.java
          												
          														

          public class ExceptionInInitializerErrorTest {
          public static void main(String[] args) {
          A a = new A();
          }
          }

          class A {
          // If the SecurityManager is not turned on, a
          // java.lang.ExceptionInInitializerError will be thrown
          static {
          if(System.getSecurityManager() == null)
          throw new SecurityException();
          }
          }

          當靜態(tài)代碼塊中發(fā)生異常時,會被自動捕捉并用 ExceptionInInitializerError 包裝該異常。在下面的輸出中可以看到這點:

          												
          														
          Exception in thread "main" java.lang.ExceptionInInitializerError
          at ExceptionInInitializerErrorTest.main(ExceptionInInitializerErrorTest.java:3)
          Caused by: java.lang.SecurityException
          at A.<clinit>(ExceptionInInitializerErrorTest.java:12)
          ... 1 more

          這個錯誤在類裝入的初始化階段拋出。修復這個錯誤的方法是檢查造成 ExceptionInInitializerError 的異常(在堆棧跟蹤的 Caused by: 下顯示)并尋找阻止拋出這個異常的方式。

          posted on 2006-05-24 16:24 想飛的魚 閱讀(530) 評論(0)  編輯  收藏 所屬分類: java
          主站蜘蛛池模板: 柘城县| 呼玛县| 新晃| 云阳县| 金塔县| 锦州市| 家居| 九寨沟县| 江达县| 静安区| 洛浦县| 长子县| 武穴市| 宜川县| 镇原县| 红桥区| 韩城市| 张家口市| 广昌县| 马公市| 屯门区| 怀柔区| 区。| 波密县| 九龙城区| 利川市| 兴国县| 石阡县| 肇州县| 大同县| 进贤县| 融水| 阳新县| 玛纳斯县| 定兴县| 北流市| 洪雅县| 神池县| 桃园县| 富裕县| 临沧市|