`

[转]java基本类装入异常

    博客分类:
  • java
阅读更多

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


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();
}
}


清单 3. A.java
												
														

public class A extends B {
}


清单 4. B.java
												
														

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();
}
}
}

在清单 5 中,调用了 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.Objectjava.lang.Cloneablejava.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 {
}


清单 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。这个情况看起来可能不太可能,但是在复杂的系统中,在把不同部分放在一起的时候,可能会发生类似的情况。

显然,要修复这个问题,必须避免循环的类层次结构。







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: 下显示)并寻找阻止抛出这个异常的方式。

分享到:
评论

相关推荐

    WebSphere Application Server V7 理解类装入器 中文翻译版

    ### WebSphere Application Server V7: 理解...通过掌握类装入器的基本概念和WebSphere特有的配置选项,开发者和系统管理员可以有效地管理应用程序的类加载过程,从而避免常见的类加载异常,并提高应用程序的整体性能。

    java语言的岗前培训

    5. **Java基本语法**: - **大小写敏感**:Java代码中的标识符(如类名、变量名)严格区分大小写。 - **自由格式**:Java允许开发者自由地缩进和排列代码,但每条语句末尾需有分号。 - **结构定义与功能执行**:...

    JAVA上百实例源码以及开源项目源代码

    Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来...

    通过Java调用本地方法.pdf

    * 装入类和获得类信息。 * 执行运行时的类型检查、垃圾回收、内存分配等。 此外,JNI技术还可以用于实现其他一些高级功能,例如Java与COM集成、CORBA集成等。 在本地方法调用中,JNI技术提供了一种机制,允许Java...

    基于JAVA的UDP服务器模型源代码.rar

    在Java中,我们可以使用`java.net.DatagramSocket`类来创建和管理UDP套接字。这个类提供了发送和接收数据报的能力。下面是一些关键概念和API: 1. **DatagramSocket**: 这是Java UDP编程的核心类,它代表一个UDP套...

    模拟文件系统

    `java.io`提供了基础的输入/输出流类,如File类,用于文件的创建、读写、删除等操作;而`java.nio`提供了更高级的非阻塞I/O功能,包括通道、缓冲区和文件系统视图等。在模拟文件系统时,开发者可能自定义了类,如`...

    超级有影响力霸气的Java面试题大全文档

     声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,...

    金蝶Apusic应用服务器V6参考b手册b.doc

    oClassLoader.loadClass()(使用指定的Classloader进行装载)oClass.forName()(使用当前类的Caller Classloader进行装载)当调用上述方法的时候,指定的Class(以类名为参数)由Classloader装入。这两个方法的行为...

    算法设计 java实现0-1背包问题

    以上就是0-1背包问题的基本概念、解决方法以及Java实现的概述。实际编程中,还需要考虑输入数据的合法性检查、异常处理等细节。通过理解和掌握这个问题,不仅可以提升算法思维,也有助于解决实际生活和工作中遇到的...

    shiro权限管理+SSM登陆+spring定时器+基本博客管理

    标题 "shiro权限管理+SSM登陆+spring定时器+基本博客管理" 涵盖了四个关键领域:Shiro权限管理、SSM框架集成、Spring定时器以及基础的博客管理系统构建。以下是对这些知识点的详细说明: 1. **Shiro权限管理**: ...

    四川大学 研究与开发实验报告

    2. **Java容器**:用来组织和管理界面元素,如在不同类中创建的box,以及在主界面中装入各个操作面板的容器。 3. **窗口监听器**:在GUI中,用于监听用户操作并响应的机制,如在各个类中实现的actionPerformed()方法...

    第12章 泛型1

    同样地,Java集合在没有泛型时,它会将所有对象都当作`Object`类型处理,当我们从集合中取出对象并需要使用时,必须进行强制类型转换,这不仅使代码变得冗余,而且可能会导致`ClassCastException`异常。以下是一个...

    JSP基础课件

    - `exception`:仅在错误页面中可用,代表异常信息。 #### 三、Servlet配置 在部署描述符`web.xml`中配置Servlet的具体信息,包括名称、类以及初始化参数等。 1. **Servlet的名字、类及其他信息** - `...

    UDP网络开发实例

    在Java中,我们主要使用`java.net.DatagramSocket`类来处理UDP通信。 首先,`UDPServer.java`是服务器端程序。它创建一个`DatagramSocket`监听特定端口,等待客户端的连接和数据。服务器通常会进入一个循环,不断...

    SpringBoot-MyBatis-DataSource(多数据源以及动态切换)

    在Spring Boot应用中,集成MyBatis作为...在实际项目中,还需要考虑事务管理、异常处理等问题,确保在切换数据源时,事务的一致性和正确性。同时,注意在不同数据源之间进行数据同步或数据隔离,以满足系统的业务需求。

    《JSP简明教程》课件

    分开内容和显示逻辑使得更新页面外观的人员不必懂得Java代码,而更新JavaBeans类的人员也不必是设计网页的行家里手,就可以用带有JavaBeans类的JSP页面来定义Web模板,以建立一个由具有相似的外观的页面组成的网站。...

    2021-2022计算机二级等级考试试题及答案No.1483.docx

    4. Java异常处理中,`try-catch`块的顺序无关紧要,但是为了代码可读性,一般将低级别的异常类放在前面,高级别的放在后面。因为高级别的异常会捕获所有低级别的异常。所以“高级别异常类的catch子句放在前面”是...

    2021-2022计算机二级等级考试试题及答案No.12251.docx

    3. Java 异常处理:Java中,异常处理的catch子句通常按照从具体到一般(低级别到高级别)的顺序排列,以便更具体异常先被捕获。 4. 数据库操作:当数据库被删除后,它包含的所有表都会被删除,而不仅仅是数据。正确...

Global site tag (gtag.js) - Google Analytics