类装入问题解密(二)-基本的类装入异常
2006-01-22 22:34
323 查看
在开始这篇文章之前,应当熟悉类装入委托模型,以及类链接的阶段和过程。我们强烈建议您从阅读这个系列的 第一篇文章 开始。
ClassNotFoundException
当应用程序试图通过类的字符串名称,使用以下三种方法装入类,但却找不到指定名称的类定义时抛出该异常。
类
类
类
所以,如果显式地装入类的尝试失败,那么就抛出
清单 1. ClassNotFoundExceptionTest.java
这个测试用例定义了一个类装入器(
因为这个测试试图使用对
通过抛出
NoClassDefFoundError
如果 Java 虚拟机或
当目前执行的类已经编译,但是找不到它的定义时,会存在 searched-for 类定义。
实际上,这意味着
清单 2 到清单 4 的测试用例产生了
清单 2. NoClassDefFoundErrorTest.java
清单 3. A.java
清单 4. B.java
这几个清单中的代码编译好之后,删除
类
显然,要修复这个特殊示例中的问题,在对应的类装入器的类路径中,必须存在类
在这个例子中,
ClassCastException
类装入器能够抛出的另一个异常是
该异常的抛出,表明代码企图把对象的类型转换成一个子类,而该对象并不是这个子类的实例。
清单 5 演示的代码示例会产生一个
清单 5. ClassCastException.java
在清单 5 中,调用了
隐式地把
显式地把这个
当程序运行时,会出现以下异常:
这个异常是由显式类型转换抛出的,因为测试用例试图把类型为
当检查对象(例如清单 5 中的
对于普通对象(非数组):对象必须是目标类的实例或目标类的子类的实例。如果目标类是接口,那么会把它当作实现了该接口的一个子类。
对于数组类型:目标类必须是数组类型或
如果违反了以上任何一条规则,那么类装入器就会抛出
UnsatisfiedLinkError
在把本机调用链接到对应的本机定义时,类装入器扮演着重要角色。如果程序试图装入一个不存在或者放错的本机库时,在链接阶段的解析过程会发生
对于声明为
当调用本机方法时,类装入器会尝试装入定义了该方法的本机库。如果找不到这个库,就会抛出这个错误。
清单 6 演示了抛出
清单 6. UnsatisfiedLinkError.java
这段代码调用本机方法
本机库的装入由调用
对于由 bootstrap 类装入器装入的类,搜索
对于由扩展类装入器装入的类,先搜索
对于由系统类装入器装入的类,搜索
在清单 6 中,
一旦理解了库装入过程所涉及的类装入器,就可以通过把库放在合适位置来解决这类问题。
ClassCircularityError
JVM 规范指定
类或接口由于是自己的超类或超接口而不能被装入。
这个错误是在链接阶段的解析过程中抛出的。这个错误有点奇怪,因为 Java 编译器不允许发生这种循环情况。但是,如果独立地编译类,然后再把它们放在一起,就可能发生这个错误。请设想以下场景。首先,编译清单 7 和清单 8 中的类:
清单 7. A.java
清单 8. B.java
然后,分别编译清单 9 和清单 10 中的类:
清单 9. A.java
清单 10. B.java
最后,采用清单 7 的类
显然,要修复这个问题,必须避免循环的类层次结构。
ClassFormatError
JVM 规范指出,抛出
负责指定所请求的编译类或接口的二进制数据形式有误。
这个异常是在类装入的链接阶段的校验过程中抛出。如果字节码发生了更改,例如主版本号或次版本号发生了更改,那么二进制数据的形式就会有误。例如,如果对字节码故意做了更改,或者在通过网络传送类文件时现出了错误,那么就可能发生这个异常。
修复这个问题的惟一方法就是获得字节码的正确副本,可能需要重新进行编译。
ExceptionInInitializerError
根据 JVM 规范,抛出
如果初始化器突然完成,抛出一些异常
如果 Java 虚拟机试图创建类
清单 8 中的代码抛出
清单 8. ExceptionInInitializerErrorTest.java
当静态代码块中发生异常时,会被自动捕捉并用
这个错误在类装入的初始化阶段抛出。修复这个错误的方法是检查造成
类装入问题解密(一)- 类装入和调试工具介绍
类装入问题解密(二)-基本的类装入异常
类装入问题解密(三)- 处理更少见的类装入问题
类装入问题解密(四)-死锁和约束
ClassNotFoundException
ClassNotFoundException是最常见的类装入异常类型。它发生在装入阶段。Java 规范对
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)。当它运行时,会出现以下异常:
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()的显式调用来进行装入,所以抛出
ClassNotFoundException。
通过抛出
ClassNotFoundException,类装入器提示,定义类时所需要的字节码在类装入器所查找的位置上不存在。这些异常修复起来通常比较简单。可以用 IBM 的 verbose 选项检查类路径,确保使用的类路径设置正确(要获得 verbose 的更多信息,请参阅本系列的 第一篇文章)。如果类路径设置正确,但是仍然看到这个错误,那么就是需要的类在类路径中不存在。要修复这个问题,可以把类移动到类路径中指定的目录或 JAR 文件中,或者把类所在的位置添加到类路径中。
NoClassDefFoundError
NoClassDefFoundError是类装入器在装入阶段抛出的另一个常见异常。JVM 规范对
NoClassDefFoundError的定义如下:
如果 Java 虚拟机或
ClassLoader实例试图装入类定义(作为正常的方法调用的一部分,或者作为使用 new 表达式创建新实例的一部分),但却没有找到类定义时抛出该异常。
当目前执行的类已经编译,但是找不到它的定义时,会存在 searched-for 类定义。
实际上,这意味着
NoClassDefFoundError的抛出,是不成功的隐式类装入的结果。
清单 2 到清单 4 的测试用例产生了
NoClassDefFoundError,因为类
B的隐式装入会失败:
清单 2. NoClassDefFoundErrorTest.java
public class NoClassDefFoundErrorTest { public static void main(String[] args) { A a = new A(); } } |
public class A extends B { } |
public class B { } |
B的类文件。当代码执行时,就会出现以下错误:
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")调用),那么就会抛出
ClassNotFoundException。
显然,要修复这个特殊示例中的问题,在对应的类装入器的类路径中,必须存在类
B。这个示例看起来可能价值不大、也不真实,但是,在复杂的有许多类的真实系统中,会因为类在打包或部署期间的遗失而发生这类情况。
在这个例子中,
A扩展了
B;但是,即使
A用其他方式引用
B,也会出现同样的问题 —— 例如,以方法参数引用或作为实例字段。如果两个类之间的关系是引用关系而不是继承关系,那么会在第一次使用
A时抛出错误,而不是在装入
A时抛出。
ClassCastException
类装入器能够抛出的另一个异常是
ClassCastException。它是在类型比较中发现不兼容类型的时候抛出的。JVM 规范指定
ClassCastException是:
该异常的抛出,表明代码企图把对象的类型转换成一个子类,而该对象并不是这个子类的实例。
清单 5 演示的代码示例会产生一个
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(); } } } |
storeItem()方法,使用一个
Integer数组、一个
int和一个字符串作为参数。但是在内部,该方法做了两件事:
隐式地把
String对象类型转换成
Object类型(用于参数列表)。
显式地把这个
Object类型转换成
Integer类型(在方法定义中)。
当程序运行时,会出现以下异常:
java.lang.ClassCastException: java.lang.String at ClassCastExceptionTest.storeItem(ClassCastExceptionTest.java:6) at ClassCastExceptionTest.main(ClassCastExceptionTest.java:12) |
String的东西转换成
Integer。
当检查对象(例如清单 5 中的
item)并把类型转换成目标类(
Integer)时,类装入器会检查以下规则:
对于普通对象(非数组):对象必须是目标类的实例或目标类的子类的实例。如果目标类是接口,那么会把它当作实现了该接口的一个子类。
对于数组类型:目标类必须是数组类型或
java.lang.Object、
java.lang.Cloneable或
java.io.Serializable。
如果违反了以上任何一条规则,那么类装入器就会抛出
ClassCastException。修复这类异常的最简单方式就是仔细检查对象要转换到的类型是否符合以上提到的规则。在某些情况下,在做类型转换之前用
instanceof进行检查是有意义的。
UnsatisfiedLinkError
在把本机调用链接到对应的本机定义时,类装入器扮演着重要角色。如果程序试图装入一个不存在或者放错的本机库时,在链接阶段的解析过程会发生
UnsatisfiedLinkError。JVM 规范指定
UnsatisfiedLinkError是:
对于声明为
native的方法,如果 Java 虚拟机找不到和它对应的本机语言定义,就会抛出该异常。
当调用本机方法时,类装入器会尝试装入定义了该方法的本机库。如果找不到这个库,就会抛出这个错误。
清单 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(); } } |
call_A_Native_Method(),该方法是在本机库
myNativeLibrary中定义的。因为这个库不存在,所以在程序运行时会发生以下错误:
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) |
System.loadLibrary()方法的类的类装入器启动 —— 在清单 6 中,就是
UnsatisfiedLinkErrorTest的类装入器。根据使用的类装入器,会搜索不同的位置:
对于由 bootstrap 类装入器装入的类,搜索
sun.boot.library.path。
对于由扩展类装入器装入的类,先搜索
java.ext.dirs,然后是
sun.boot.library.path,然后是
java.library.path。
对于由系统类装入器装入的类,搜索
sun.boot.library.path,然后是
java.library.path。
在清单 6 中,
UnsatisfiedLinkErrorTest类是由系统类装入器装入的。要装入所引用的本机库,这个类装入器先查找
sun.boot.library.path,然后查找
java.library.path。因为在两个位置中都没有需要的库,所以类装入器抛出
UnsatisfiedLinkageError。
一旦理解了库装入过程所涉及的类装入器,就可以通过把库放在合适位置来解决这类问题。
ClassCircularityError
JVM 规范指定
ClassCircularityError的抛出条件是:
类或接口由于是自己的超类或超接口而不能被装入。
这个错误是在链接阶段的解析过程中抛出的。这个错误有点奇怪,因为 Java 编译器不允许发生这种循环情况。但是,如果独立地编译类,然后再把它们放在一起,就可能发生这个错误。请设想以下场景。首先,编译清单 7 和清单 8 中的类:
清单 7. A.java
public class A extends B { } |
public class B { } |
清单 9. A.java
public class A { } |
public class B extends A { } |
A和清单 10 的类
B,并运行一个应用程序,试图装入
A或者
B。这个情况看起来可能不太可能,但是在复杂的系统中,在把不同部分放在一起的时候,可能会发生类似的情况。
显然,要修复这个问题,必须避免循环的类层次结构。
ClassFormatError
JVM 规范指出,抛出
ClassFormatError的条件是:
负责指定所请求的编译类或接口的二进制数据形式有误。
这个异常是在类装入的链接阶段的校验过程中抛出。如果字节码发生了更改,例如主版本号或次版本号发生了更改,那么二进制数据的形式就会有误。例如,如果对字节码故意做了更改,或者在通过网络传送类文件时现出了错误,那么就可能发生这个异常。
修复这个问题的惟一方法就是获得字节码的正确副本,可能需要重新进行编译。
ExceptionInInitializerError
根据 JVM 规范,抛出
ExceptionInInitializer的情况是:
如果初始化器突然完成,抛出一些异常
E,而且
E的类不是
Error或者它的某个子类,那么就会创建
ExceptionInInitializerError类的一个新实例,并用
E作为参数,用这个实例代替
E。
如果 Java 虚拟机试图创建类
ExceptionInInitializerError的新实例,但是因为出现
Out-Of-Memory-Error而无法创建新实例,那么就抛出
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(); } } |
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:下显示)并寻找阻止抛出这个异常的方式。
类装入问题解密(一)- 类装入和调试工具介绍
类装入问题解密(二)-基本的类装入异常
类装入问题解密(三)- 处理更少见的类装入问题
类装入问题解密(四)-死锁和约束
相关文章推荐
- 类装入问题解密,第 2 部分: 基本的类装入异常
- 类装入问题解密,第 2 部分: 基本的类装入异常
- [收藏] 类装入问题解密
- 类装入问题解密,第 4 部分: 死锁和约束
- [收藏] 类装入问题解密
- Android问题分享:DownloadManager基本用法及发生java.lang.SecurityException异常的解决办法
- CPU利用率异常分析讨论会-基本概念类问题
- 类装入问题解密(一)- 类装入和调试工具介绍
- CPU利用率异常分析讨论会-基本概念类问题
- C# DES解密异常问题
- 类装入问题解密(三)- 处理更少见的类装入问题
- 类装入问题解密(四)-死锁和约束
- 异常的抛出基本问题
- 类装入问题解密,第 1 部分: 类装入和调试工具介绍
- 基本的类装入异常
- 类装入问题解密,第 3 部分: 处理更少见的类装入问题
- 菜鸟学习OGRE和天龙八部之十: frame动画基本搞定,遇到点问题
- xtrabackup的备份配置异常问题解析
- Android异常问题记录
- 大型网站架构基本问题