原文鏈接:http://www-128.ibm.com/developerworks/cn/java/j-dclp2.html
ClassNotFoundException
ClassNotFoundException
是最常見(jiàn)的類裝入異常類型。它發(fā)生在裝入階段。Java 規(guī)范對(duì) ClassNotFoundException
的描述是這樣的:
當(dāng)應(yīng)用程序試圖通過(guò)類的字符串名稱,使用以下三種方法裝入類,但卻找不到指定名稱的類定義時(shí)拋出該異常。
- 類
Class
中的 forName()
方法。
- 類
ClassLoader
中的 findSystemClass()
方法。
- 類
ClassLoader
中的 loadClass()
方法。
所以,如果顯式地裝入類的嘗試失敗,那么就拋出 ClassNotFoundException
。清單 1 中的測(cè)試用例提供的示例代碼拋出了一個(gè) 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(); } } }
|
這個(gè)測(cè)試用例定義了一個(gè)類裝入器(MyClassLoader
),用于裝入一個(gè)不存在的類(DoesNotExist
)。當(dāng)它運(yùn)行時(shí),會(huì)出現(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)
|
因?yàn)檫@個(gè)測(cè)試試圖使用對(duì) loadClass()
的顯式調(diào)用來(lái)進(jìn)行裝入,所以拋出 ClassNotFoundException
。
通過(guò)拋出 ClassNotFoundException
,類裝入器提示,定義類時(shí)所需要的字節(jié)碼在類裝入器所查找的位置上不存在。這些異常修復(fù)起來(lái)通常比較簡(jiǎn)單。可以用 IBM 的 verbose 選項(xiàng)檢查類路徑,確保使用的類路徑設(shè)置正確(要獲得 verbose 的更多信息,請(qǐng)參閱本系列的 第一篇文章)。如果類路徑設(shè)置正確,但是仍然看到這個(gè)錯(cuò)誤,那么就是需要的類在類路徑中不存在。要修復(fù)這個(gè)問(wèn)題,可以把類移動(dòng)到類路徑中指定的目錄或 JAR 文件中,或者把類所在的位置添加到類路徑中。
NoClassDefFoundError
NoClassDefFoundError
是類裝入器在裝入階段拋出的另一個(gè)常見(jiàn)異常。JVM 規(guī)范對(duì) NoClassDefFoundError
的定義如下:
如果 Java 虛擬機(jī)或 ClassLoader
實(shí)例試圖裝入類定義(作為正常的方法調(diào)用的一部分,或者作為使用 new 表達(dá)式創(chuàng)建新實(shí)例的一部分),但卻沒(méi)有找到類定義時(shí)拋出該異常。
當(dāng)目前執(zhí)行的類已經(jīng)編譯,但是找不到它的定義時(shí),會(huì)存在 searched-for 類定義。
實(shí)際上,這意味著 NoClassDefFoundError
的拋出,是不成功的隱式類裝入的結(jié)果。
清單 2 到清單 4 的測(cè)試用例產(chǎn)生了 NoClassDefFoundError
,因?yàn)轭?B
的隱式裝入會(huì)失敗:
清單 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
這幾個(gè)清單中的代碼編譯好之后,刪除 B
的類文件。當(dāng)代碼執(zhí)行時(shí),就會(huì)出現(xiàn)以下錯(cuò)誤:
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
擴(kuò)展了類 B
;所以,當(dāng)類 A
裝入時(shí),類裝入器會(huì)隱式地裝入類 B
。因?yàn)轭?B
不存在,所以拋出 NoClassDefFoundError
。如果顯式地告訴類裝入器裝入類 B
(例如通過(guò) loadClass("B")
調(diào)用),那么就會(huì)拋出 ClassNotFoundException
。
顯然,要修復(fù)這個(gè)特殊示例中的問(wèn)題,在對(duì)應(yīng)的類裝入器的類路徑中,必須存在類 B
。這個(gè)示例看起來(lái)可能價(jià)值不大、也不真實(shí),但是,在復(fù)雜的有許多類的真實(shí)系統(tǒng)中,會(huì)因?yàn)轭愒诖虬虿渴鹌陂g的遺失而發(fā)生這類情況。
在這個(gè)例子中,A
擴(kuò)展了 B
;但是,即使 A
用其他方式引用 B
,也會(huì)出現(xiàn)同樣的問(wèn)題 —— 例如,以方法參數(shù)引用或作為實(shí)例字段。如果兩個(gè)類之間的關(guān)系是引用關(guān)系而不是繼承關(guān)系,那么會(huì)在第一次使用 A
時(shí)拋出錯(cuò)誤,而不是在裝入 A
時(shí)拋出。
ClassCastException
類裝入器能夠拋出的另一個(gè)異常是 ClassCastException
。它是在類型比較中發(fā)現(xiàn)不兼容類型的時(shí)候拋出的。JVM 規(guī)范指定 ClassCastException
是:
該異常的拋出,表明代碼企圖把對(duì)象的類型轉(zhuǎn)換成一個(gè)子類,而該對(duì)象并不是這個(gè)子類的實(shí)例。
清單 5 演示的代碼示例會(huì)產(chǎn)生一個(gè) 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()
方法,使用一個(gè) Integer
數(shù)組、一個(gè) int
和一個(gè)字符串作為參數(shù)。但是在內(nèi)部,該方法做了兩件事:
- 隱式地把
String
對(duì)象類型轉(zhuǎn)換成 Object
類型(用于參數(shù)列表)。
- 顯式地把這個(gè)
Object
類型轉(zhuǎn)換成 Integer
類型(在方法定義中)。
當(dāng)程序運(yùn)行時(shí),會(huì)出現(xiàn)以下異常:
java.lang.ClassCastException: java.lang.String at ClassCastExceptionTest.storeItem(ClassCastExceptionTest.java:6) at ClassCastExceptionTest.main(ClassCastExceptionTest.java:12)
|
這個(gè)異常是由顯式類型轉(zhuǎn)換拋出的,因?yàn)闇y(cè)試用例試圖把類型為 String
的東西轉(zhuǎn)換成 Integer
。
當(dāng)檢查對(duì)象(例如清單 5 中的 item
)并把類型轉(zhuǎn)換成目標(biāo)類(Integer
)時(shí),類裝入器會(huì)檢查以下規(guī)則:
-
對(duì)于普通對(duì)象(非數(shù)組):對(duì)象必須是目標(biāo)類的實(shí)例或目標(biāo)類的子類的實(shí)例。如果目標(biāo)類是接口,那么會(huì)把它當(dāng)作實(shí)現(xiàn)了該接口的一個(gè)子類。
-
對(duì)于數(shù)組類型:目標(biāo)類必須是數(shù)組類型或
java.lang.Object
、java.lang.Cloneable
或 java.io.Serializable
。
如果違反了以上任何一條規(guī)則,那么類裝入器就會(huì)拋出 ClassCastException
。修復(fù)這類異常的最簡(jiǎn)單方式就是仔細(xì)檢查對(duì)象要轉(zhuǎn)換到的類型是否符合以上提到的規(guī)則。在某些情況下,在做類型轉(zhuǎn)換之前用 instanceof
進(jìn)行檢查是有意義的。
UnsatisfiedLinkError
在把本機(jī)調(diào)用鏈接到對(duì)應(yīng)的本機(jī)定義時(shí),類裝入器扮演著重要角色。如果程序試圖裝入一個(gè)不存在或者放錯(cuò)的本機(jī)庫(kù)時(shí),在鏈接階段的解析過(guò)程會(huì)發(fā)生 UnsatisfiedLinkError
。JVM 規(guī)范指定 UnsatisfiedLinkError
是:
對(duì)于聲明為 native
的方法,如果 Java 虛擬機(jī)找不到和它對(duì)應(yīng)的本機(jī)語(yǔ)言定義,就會(huì)拋出該異常。
當(dāng)調(diào)用本機(jī)方法時(shí),類裝入器會(huì)嘗試裝入定義了該方法的本機(jī)庫(kù)。如果找不到這個(gè)庫(kù),就會(huì)拋出這個(gè)錯(cuò)誤。
清單 6 演示了拋出 UnsatisfiedLinkError
的測(cè)試用例 :
清單 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)用本機(jī)方法 call_A_Native_Method()
,該方法是在本機(jī)庫(kù) myNativeLibrary
中定義的。因?yàn)檫@個(gè)庫(kù)不存在,所以在程序運(yùn)行時(shí)會(huì)發(fā)生以下錯(cuò)誤:
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)
|
本機(jī)庫(kù)的裝入由調(diào)用 System.loadLibrary()
方法的類的類裝入器啟動(dòng) —— 在清單 6 中,就是 UnsatisfiedLinkErrorTest
的類裝入器。根據(jù)使用的類裝入器,會(huì)搜索不同的位置:
- 對(duì)于由 bootstrap 類裝入器裝入的類,搜索
sun.boot.library.path
。
- 對(duì)于由擴(kuò)展類裝入器裝入的類,先搜索
java.ext.dirs
,然后是 sun.boot.library.path
,然后是 java.library.path
。
- 對(duì)于由系統(tǒng)類裝入器裝入的類,搜索
sun.boot.library.path
,然后是 java.library.path
。
在清單 6 中,UnsatisfiedLinkErrorTest
類是由系統(tǒng)類裝入器裝入的。要裝入所引用的本機(jī)庫(kù),這個(gè)類裝入器先查找 sun.boot.library.path
,然后查找 java.library.path
。因?yàn)樵趦蓚€(gè)位置中都沒(méi)有需要的庫(kù),所以類裝入器拋出 UnsatisfiedLinkageError
。
一旦理解了庫(kù)裝入過(guò)程所涉及的類裝入器,就可以通過(guò)把庫(kù)放在合適位置來(lái)解決這類問(wèn)題。
ClassCircularityError
JVM 規(guī)范指定 ClassCircularityError
的拋出條件是:
類或接口由于是自己的超類或超接口而不能被裝入。
這個(gè)錯(cuò)誤是在鏈接階段的解析過(guò)程中拋出的。這個(gè)錯(cuò)誤有點(diǎn)奇怪,因?yàn)?Java 編譯器不允許發(fā)生這種循環(huán)情況。但是,如果獨(dú)立地編譯類,然后再把它們放在一起,就可能發(fā)生這個(gè)錯(cuò)誤。請(qǐng)?jiān)O(shè)想以下場(chǎng)景。首先,編譯清單 7 和清單 8 中的類:
清單 7. A.java
public class A extends B { }
|
清單 8. B.java
然后,分別編譯清單 9 和清單 10 中的類:
清單 9. A.java
清單 10. B.java
public class B extends A { }
|
最后,采用清單 7 的類 A
和清單 10 的類 B
,并運(yùn)行一個(gè)應(yīng)用程序,試圖裝入 A
或者 B
。這個(gè)情況看起來(lái)可能不太可能,但是在復(fù)雜的系統(tǒng)中,在把不同部分放在一起的時(shí)候,可能會(huì)發(fā)生類似的情況。
顯然,要修復(fù)這個(gè)問(wèn)題,必須避免循環(huán)的類層次結(jié)構(gòu)。
ClassFormatError
JVM 規(guī)范指出,拋出 ClassFormatError
的條件是:
負(fù)責(zé)指定所請(qǐng)求的編譯類或接口的二進(jìn)制數(shù)據(jù)形式有誤。
這個(gè)異常是在類裝入的鏈接階段的校驗(yàn)過(guò)程中拋出。如果字節(jié)碼發(fā)生了更改,例如主版本號(hào)或次版本號(hào)發(fā)生了更改,那么二進(jìn)制數(shù)據(jù)的形式就會(huì)有誤。例如,如果對(duì)字節(jié)碼故意做了更改,或者在通過(guò)網(wǎng)絡(luò)傳送類文件時(shí)現(xiàn)出了錯(cuò)誤,那么就可能發(fā)生這個(gè)異常。
修復(fù)這個(gè)問(wèn)題的惟一方法就是獲得字節(jié)碼的正確副本,可能需要重新進(jìn)行編譯。
ExceptionInInitializerError
根據(jù) JVM 規(guī)范,拋出 ExceptionInInitializer
的情況是:
- 如果初始化器突然完成,拋出一些異常
E
,而且 E
的類不是 Error
或者它的某個(gè)子類,那么就會(huì)創(chuàng)建 ExceptionInInitializerError
類的一個(gè)新實(shí)例,并用 E
作為參數(shù),用這個(gè)實(shí)例代替 E
。
- 如果 Java 虛擬機(jī)試圖創(chuàng)建類
ExceptionInInitializerError
的新實(shí)例,但是因?yàn)槌霈F(xiàn) Out-Of-Memory-Error
而無(wú)法創(chuàng)建新實(shí)例,那么就拋出 OutOfMemoryError
對(duì)象作為代替。
清單 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(); } }
|
當(dāng)靜態(tài)代碼塊中發(fā)生異常時(shí),會(huì)被自動(dòng)捕捉并用 ExceptionInInitializerError
包裝該異常。在下面的輸出中可以看到這點(diǎn):
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
|
這個(gè)錯(cuò)誤在類裝入的初始化階段拋出。修復(fù)這個(gè)錯(cuò)誤的方法是檢查造成 ExceptionInInitializerError
的異常(在堆棧跟蹤的 Caused by:
下顯示)并尋找阻止拋出這個(gè)異常的方式。
posted on 2006-05-24 16:24
想飛的魚(yú) 閱讀(534)
評(píng)論(0) 編輯 收藏 所屬分類:
java