Java类加载机制


Java 类加载机制(一) 译:ayi  译文疏漏,请多多指点 原文:http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html  注:因内容太多,分为一、二两篇文章 类加载是 java 特性的一个很重要的部分。尽管,java 中“advanced topics”的发展,使 java  的类加载机制地位有所下降。但每位编程者都应该知道这部分的工作机制,以及怎样去配 合其工作。这可以使我们节省很多时间,而不必要浪费在调试ClassNotFoundException,  ClassCastException, 等。 这篇文章将从最基本的开始,比如代码和数据的关系,以及他们怎么样关系起来形成一 个实例或者对象。 然后将会说到, java 中怎样通过类加载器把代码加载到 JVM 中, 以及 java  中实现的主要的几种类型的类加载器。在这篇文章中,然后我们将会了解到 java 类加载机 制的内幕,我们将使用最基本的代码来描述,这些代码执行于类加载器之后,但在加载一 个类之前。在接下来的部分将使用一些例子来证实,对于开发者继承和开发自己的类加载 器的必要性。接着将告诉你们怎样编写自己的类加载器,以及怎样使用它们去创建一个一 般的能加载包括远程客户端辅助代码的类加载器引擎,以及怎样把它在 JVM 中定义,实例 化,然后执行。习惯上,把 J2EE­specific components 中说明的作为 java 类加载的规范,这 篇文章正是从这本手册总结来的。 类和数据 一个类代表一段要执行的代码,然而数据则代表与这些代码相关联的某种状态。状态可 以改变,代码不能改变。我们把一种特定状态与一个类关联起来时,就得到了这个类的一 个实例。所以同一个类的不同实例有不同的状态,但都参照相同的代码。在 java 中,一个 类通常它的代码就包含在一个 .class 文件中,虽然其中也包括异常。然而,在 java 运行时, 每个类都会构造一个超类对象(first­class object),它们其实是 java.lang.Class 的实例。不 论何时编译一个 java 文件,编译器都会在编译后的字节码中嵌入一个 public, static, final 型 的字段 class,这个字段表示的就是一个 java.lang.Class 型的实例。因为它是 public 类型的, 我们可以通过标识符来访问它,像这样:  java.lang.Class klass = Myclass.class;  只要一个类被加载到  JVM,相同的类(强调:相同的类)将不会被重复加载。这将产 生一个问题,什么才是相同的类?一个对象有一种特定状态和标识,对象总是与它所属类 联系在一起,与这种状况相似,一个被加载到 JVM 中类也有特定的标识,接下来我们就阐 述: 在  java  中,一个类通过认证的类全名来唯一标识。认证的类全名是由包名和类名两部 分组成。但是在一个类被加载到 JVM 中则是通过认证的类全名,还有加载这个类的加载器 来唯一标识。因此,一个类的类名为 C1,包名为 Pg,被类加载器类 KClassLoader 的一个 实例 k1 加载,则 C1,也就是 C1.class ,的类实例,在 JVM 中将被解释为(C1,Pg,k1)。 这就意味着两个不同的类加载器实(Cl, Pg, kl1) 和 (Cl, Pg, kl2) ,加载的类在 JVM 中将有不 同的类实例对象,不是类型可比型(type­compatible)的。在  JVM  中有多少个类加载器实 例呢?下面,我们将讲解这个。 类加载器 在 java 中, 每个类都会被 java.lang.ClassLoader 的一个实例加载。 ClassLoader 类处于 java.lang  包下面,开发者可以自由的创建它的子类,添加自己功能的类加载器。 每当敲入 java MyMainClass,一个新的 JVM 开始时,引导类加载器(bootstrap class loader ) 首先会把 java 中的一些关键类,像 java.lang.Objent,和运行时的代码载入内存。这些运行 时类打包在 JRE\lib\rt.jar 文件中。因为是一个本地的接口,我们并不能从 java 文档中得到 引 导 类 加 载 器 ( bootstrap class loader  ) 信 息 。 也 正 是 这 个 原 因 , 引 导 类 加 载 器 (bootstrap class loader )的表现也根据 JVM 的不同而异。 比如,如果我们试图得到一个核心 java 运行时类的一个类加载器,我们将得到 null 值,如 下:  log(java.lang.String.class.getClassLoader());  下面要说到的是 java 扩展类加载器。在 java.ext.dirs 路径下面,我们可以放 java 扩展类库, 这样我们可以获得超出 java 核心运行时类的特性。扩展类加载器(ExtClassLoader)将会加 载 java.ext.dirs 目录下的所有 .jar 文件。开发者可以为自己的应用增加新的 .jar 文件 或者 类 库,只要他把它们添加到 java.ext.dirs 目录下面以至于能被扩展类加载器找到。 在 Sun 的 java 指南中,文章“理解扩展类加载”(Understanding Extension Class Loading) 对以上三个类加载器路径有更详尽的解释,这是其他几个 JDK 中的类加载器  java.net.URLClassLoader  java.security.SecureClassLoader  java.rmi.server.RMIClassLoader  sun.applet.AppletClassLoader  java.lang.Thread,包含了  public ClassLoader getContextClassLoader()方法,这一方法返回针 对一具体线程的上下文环境类加载器。上下文加载器是线程创建着提供的,用以来加载线 程运行时需要的类和资源。如果没有设定,默认的是父线程的上下文类加载器。最原始的 上下文类加载器由加载 application 应用程序的类加载器建立。 类加载器怎样工作 所 有 的 类 加 载 器 , 除 了 引 导 类 加 载 器 , 都 一 个 父 类 加 载 器 。 而 且 , 它 们 都 是  java.lang.ClassLoader  类型的。上面两句话是不同的,而且对与开发者开发的任何一个类加 载器的正常工作来说都非常重要。在这里,最重要的是怎样正确的设置父类加载器。类加 载器的父类加载器实例会负责加载此类加载器类。(记住: 一个类加载器本身也是一个类。) 在一个类加载器外部请求一个类时,使用  loadClass() 方法。这个方法的具体工作,我们可 以从源代码来看:  protected synchronized Class loadClass  (String name, boolean resolve)  throws ClassNotFoundException{  // First check if the class is already loaded  Class c = findLoadedClass(name);  if (c == null) {  try {  if (parent != null) {  c = parent.loadClass(name, false);  } else {  c = findBootstrapClass0(name);  }  } catch (ClassNotFoundException e) { // If still not found, then invoke  // findClass to find the class.  c = findClass(name);  }  }  if (resolve) {  resolveClass(c);  }  return c;  }  设置父类加载器,我们有两种方法,在 ClassLoader 的构造方法中。  public class MyClassLoader extends ClassLoader{  public MyClassLoader(){  super(MyClassLoader.class.getClassLoader());  }  } 或者  public class MyClassLoader extends ClassLoader{  public MyClassLoader(){  super(getClass().getClassLoader());  }  }  第一种方法更为常用,因为在构造方法中使用 getClass() 方法是不提倡的,因为对象初 始化仅在构造方法结束后才会完成。因此,如果正确设置了类加载器的父类加载器,不论 什么时候从类加载器实例请求一个类时,如果此类加载器不能加载此类,则首先会交给它 的父类加载器处理。如果此父类加载器也不能加载那个类,则又会交给上一层的父类加载 器, 依此类推。 但是如果 findBootstrapClass0()方法也未能加载那个类时, 就会唤醒 findClass()  去处理。findClass()的默认的实现是抛 ClassNotFoundException 异常。所以开发者需要继承  java.lang.ClassLoader 来实现用户自编写的类加载器。findClass()默认的实现如下:  protected Class findClass(String name)  throws ClassNotFoundException {  throw new ClassNotFoundException(name);  }  深入 findClass()方法,类加载器需要从其它资源获取字节码。这些资源可以是文件系统、网 络 URL、数据库、其它的可以把字节码转换为流的应用程序,或者能够产生与 java 特性相 适应的字节码的类似资源。你可以使用BCEL (Byte Code Engineering Library),它能非常方便 的根据运行时的迹象创建类 。BCEL 已经成功的应用在了一些地方,如编译器、优化程序、 模糊程序 (obsfuscators)、 代码生成器、 分析工具。 只要这个这些字节码被重新获取, findClass() 方法将会调用  defineClass()方法,而且这时运行时(runtime )环境是非常特殊的对于具体 哪一个类加载器实例去调用 defineClass()这个方法。 所有, 两个类加载器实例从两个相同的、 或者不同的资源加载字节码时,这些加载的类都是不同的。 在  java  语 言 详 解 ( Java language specification ) 中 , 给 出 了 在  java  执 行 引 擎 (Java Execution Engine)中加载、链接、初始化的类和接口等过程的详细解释。 图  1  展示了一 个带有  main  方 法的 应用 程序类  MyMainClass 。正如 前面所 说的,  MyMainClass.class 将被  AppClassLoader  加载,MyMainClass 创建两个加载器类实例,  CustomClassLoader1 和 CustomClassLoader2,他们都能从某些资源(比如说:网络)中加载 第四个类 Target 的字节码。 这就意味着 Target 这个类的定义超出了应用程序的 class path 或 者扩展  class path 范围。在这种情况下,如果  MyMainClass 让客户加载器实例去加载  Target 类。 Target 将同时被 CustomClassLoader1 和 CustomClassLoader2 加载和定义。在 java  中这样就会有严重的问题。如果在 Target 中包含一段静态(static)的初始化代码,如果我 们要求这段代码执行且仅贝被执行一次,在我们目前的情况下,这段代码将被执行两次。 在两个 CustomClassLoader 中,都执行了一次。如果 Target 被两个 CustomClassLoader 同时 初始化,他将会有两个实例 target1 和 target2 ,正如下面图 1 所示的,target1 和 target2 是不 可比的。也就是说,在 java 中不能执行这段代码:  Target target3 = (Target) target2;  上面的代码将会抛出 ClassCastException 异常。这是因为 JVM 把他们看做两个独立的不同 的类类型,因为它们被不同类型的 ClassLoader 实例加载。如果 MyMainClass 使用的不是不 同类型的 ClassLoader(这里:CustomClassLoader1 和 CustomClassLoader2) 实例,使用同 一类型的 ClassLoader 的两个实例来加载, 情况也将是一样的。 这将在后面通过代码来证实。 图 1 在同一个 JVM 中多个 ClassLoader 加载同一个类 Target  关 于 类 加 载 、 定 义 、 链 接 的 过 程 更 加 详 细 的 解 释 在  Andreas Schaefer  的 文 章  Inside Class Loaders中 为什么我们要使用我们自己的类加载器? 开发者编写自己的类加载器的一个理由是控制 JVM 的行为。在 java 中区分一个类是通 过包名加类名。对于实现了 java.io.Serializable 接口的类,serialVersionUID 将在类的版本化 中担当一个重要角色。这种流唯一标志(  stream­unique identifier  )是一个由类名、接口名、 方法以及字段生成的一个 64 位的哈希码。除了这些,没有其它的直接的结构来版本化一个 类。单从技术上来说,如果上面所说的匹配的话,这些类就是相同的版本。 想一下这种情况,当我们想要去开发一个面向一般性的执行引擎,它能够执行任何实现了 某个特定接口的任务。当这些任务被提交到引擎,引擎首先需要去加载这些任务的代码。 假如很多不同的客户提交不同的任务(就是:不同的代码)给我们的引擎,碰巧,这些任 务都有相同的类名和包名。问题产生了,引擎将要为不同的客户调用上下文加载不同的客 户版本,使客户能得到它们想要的正确结果吗?这将在下面,通过加载相同的客户代码来 证实。在 samepath 和 differentversions 这两个目录中,均包括独立的代码来证实这个原理。 图 2 展示了示例在 samepath, differentversions, 和 differentversionspush 这三个子目录中。 图 2 示例的目录结构 在 samepath 目录,我们有均 version.Version 类分别在它的子目录 v1 和 v2 下面。这两个类 均有相同的类名和包名。唯一的不同是:  public void fx(){  log("this = " + this + "; Version.fx(1).");  }  在 v1 中,我们有 Version.fx(1)在 log 语句中;而在 v2 中,是 Version.fx(2)。在相同的路径 下,这两个不同版本的类仅有这点区别。现在执行 Test 如下: set CLASSPATH=.;%CURRENT_ROOT%\v1;%CURRENT_ROOT%\v2  %JAVA_HOME%\bin\java Test  在图 3 中,我们将看到控制台的输出,Version.fx(1) 被加载了,因为 ClassLoader 在 classpath  中首先找到了 v1 中的类。 图 3 在版本 1 的 test 类的路径放在前面 我们稍微改变一下 classpath 中个路径的先后顺序,再次运行一次:  set CLASSPATH=.;%CURRENT_ROOT%\v2;%CURRENT_ROOT%\v1  %JAVA_HOME%\bin\java Test  控制台的输出改变了,如图 4。现在,Version.fx(2) 被执行了,因为 class loader 先找到了这 个版本的类。 图 4 在版本 2 的 test 类的路径放在前面 从上面的可以看出,class loader 会加载在 classpath 中先找到的类。如果我们不这样,从 v1、  v2 中删除 version.Version ,而把它们打包在一个 myextension.jar 的.jar 文件中,并把它们放 到 java.ext.dirs 目录下面,重新测试,我们将看到,它将不再被 AppClassLoader 加载,而是 被扩展类加载器加载。如图 5 所示: 图 5 AppClassLoader 和 ExtClassLoader 类加载器 进一步来研究这个例子,目录 differentversions 下面有一个 RMI 执行引擎。客户能够提交任 何实现了 common.TaskIntf 接口的任务给这个引擎。两个子目录 client1 和 client2 均包含了 稍有区别的这个类 client.TaskImpl。它们的区别如下代码所示:  static{  log("client.TaskImpl.class.getClassLoader  (v1) : " + TaskImpl.class.getClassLoader()); }  public void execute(){  log("this = " + this + "; execute(1)");  }  在 client1 的 log 语句中执行的是 getClassLoader(v1) 是 execute(1), 在 client2 的 log 语句中执 行的是 getClassLoader(v2) 是 execute(2)。而且,在启动 RMI 服务器引擎的脚本中,我们不 妨把 client2 路径放在前面:  CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server;  %CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1  %JAVA_HOME%\bin\java server.Server  在图  6、7、8  的屏幕截屏中展示了这时的情况。这时,在两个客户虚拟机中,单独的  client.TaskImpl 类,被加载、初始化,并传递到了服务器端虚拟机的执行引擎中。从服务器 端的控制台可以看出,client.TaskImpl 仅被加载了一次。这个单独“版本”的代码在服务器端 被用来产生许多 client.TaskImpl 实例,来执行任务。 图 6 服务器端控制台输出 图 6 展示了服务器端引擎控制台的输出,它加载、执行两个各自的客户请求(如图 7、8 所 示)。要指出的是,在这里代码仅被执行了一次(很明显,从静态初始化语句块中的  log  语句来看),但是这个方法被执行了两次,每个客户的一次调用。 图 7 客户 1 的控制台 在图 7 中,服务器端提供的类 TaskImpl 包括语句 client.TaskImpl.class.getClassLoader(v1) 被 加 载 入 了 客 户 虚 拟 机 。 在 图  8  中 , 客 户 虚 拟 机 加 载 了 不 同 的 , 包 含  client.TaskImpl.class.getClassLoader(v2)的,TaskImpl 类代码。 图 8 客户 2 的控制台 这里,各自的 client.TaskImpl 类被加载、初始化,并且交给服务器端虚拟机来执行。再次看 一下图 6 所展示的服务器端的控制台,显示了 client.TaskImpl 加载且仅被加载了一次。这个 单独的代码将被用来在服务器端生成  client.TaskImpl 实例。Client1  将不高兴了,因为它的 语句 client.TaskImpl(v1)并没有被执行,而是其它的代码在执行当它在客户端调用时。我们 怎样处理这种情况呢?答案是实现客户的类加载器。 客户类加载器 较好的控制类加载的解决办法是实现自己的客户类加载器。任何客户类加载器都必须直接 或间接继承 java.lang.ClassLoader。而且在构造函数中,我们也必须把父类设置为类加载器。 然后,我们要重写  findClass()方法。在  differentversionspush  目录中包括了一个名为  FileSystemClassLoader 的类加载器。目录结果如图 9 所示: 图 9 客户类加载器关系 下面是在 common.FileSystemClassLoader 实现的主要方法:  public byte[] findClassBytes(String className){  try{  String pathName = currentRoot + File.separatorChar + className.  replace('.', File.separatorChar)  + ".class";  FileInputStream inFile = new  FileInputStream(pathName);  byte[] classBytes = new  byte[inFile.available()];  inFile.read(classBytes);  return classBytes;  }  catch (java.io.IOException ioEx){  return null;  }  }  public Class findClass(String name)throws  ClassNotFoundException{  byte[] classBytes = findClassBytes(name);  if (classBytes==null){ throw new ClassNotFoundException();  }  else{ return defineClass(name, classBytes,  0, classBytes.length);  }  }  public Class findClass(String name, byte[]  classBytes)throws ClassNotFoundException{  if (classBytes==null){  throw new ClassNotFoundException(  "(classBytes==null)");  }  else{ return defineClass(name, classBytes,  0, classBytes.length);  } }  public void execute(String codeName,  byte[] code){  Class klass = null;  try{  klass = findClass(codeName, code);  TaskIntf task = (TaskIntf)  klass.newInstance();  task.execute();  }  catch(Exception exception){  exception.printStackTrace();  }  }  这个类被客户用来转换 client.TaskImpl(v1)为 byte[]。这个 byte[]将被传递给服务器端执行引 擎。在服务器端,相同的类将被用来从字节数组逆转定义这个类。客户端代码如下:  public class Client{ public static void main (String[] args){  try{  byte[] code = getClassDefinition  ("client.TaskImpl");  serverIntf.execute("client.TaskImpl",  code);  }  catch(RemoteException remoteException){  remoteException.printStackTrace();  }  }  private static byte[] getClassDefinition  (String codeName){  String userDir = System.getProperties().  getProperty("BytePath"); FileSystemClassLoader fscl1 = null;  try{  fscl1 = new FileSystemClassLoader  (userDir);  }  catch(FileNotFoundException  fileNotFoundException){  fileNotFoundException.printStackTrace();  }  return fscl1.findClassBytes(codeName);  }  }  从服务器端来看,从客户端接受的代码将交给客户类加载器。客户类加载器首先从接收到 的字节数组中逆定义类,实例化,然后执行。这里值得指出的是,对于每个客户请求,都 将 使 用 各 自 的  FileSystemClassLoader  实 例 来 加 载 提 供 的  client.TaskImpl 。 而 且 ,  client.TaskImpl  类 并 不 在 服 务 器 端 的  classpath  范 围 内 。 这 就 意 味 着 , 当 我 们 在  FileSystemClassLoader  中 调 用  findClass()  时 ,  findClass()  会 从 内 部 调 用  defineClass(), client.TaskImpl  将 被 各 自 的 类 加 载 器 实 例 加 载 。 当 有 一 个 新 的  FileSystemClassLoader 实例来加载时,照样从逆定义类开始重新做一遍。所以,对每个客户 调用,类  client.TaskImpl 都被重新定义了,使我们能够在同一个虚拟机中执行“不同版本”  的 client.TaskImpl 代码。 public void execute(String codeName, byte[] code)throws RemoteException{  FileSystemClassLoader fileSystemClassLoader = null;  try{  fileSystemClassLoader = new FileSystemClassLoader();  fileSystemClassLoader.execute(codeName, code);  }  catch(Exception exception){  throw new RemoteException(exception.getMessage());  }  }  目录  differentversionspush 下的  examples。服务器和客户端控制台输出,如图  10 、11、12  中所示: 图 10 服务器端的 Custom class loader 的执行结果 图 10 展示了客户类加载器的虚拟机控制台。我们可以看到 client.TaskImpl 被执行了不只一 次。事实上,对每个客户执行上下文环境,这个类被新加载和初始化一次。 图 11 客户类加载器引擎 Client 1  在图 11 中,包含了语句 client.TaskImpl.class.getClassLoader(v1) 的 TaskImpl 类的这些代码 在 客 户 端 被 加 载 , 然 后 被 放 到 服 务 器 端 执 行 。 图  12  , 包 含 了 语 句  client.TaskImpl.class.getClassLoader(v2) 的 TaskImpl 的不同类在客户端 2 的加载情况,并在 服务器端执行。 图 12 客户类加载引擎 ,client2  这例子向我们展示了,当在同一个 JVM 中有“不同版本”的代码时,我们怎样使用各自的类 加载器实例来实现一一对应的边对边执行。 类加载器在 J2EE 中 在 j2ee 中,类加载器倾向于在不同的时间段移除和重新加载类。这种情况在某些实现中存 在,在某些中没有。web 服务器可能会移除以前的一个已经加载的 servlet 实例,可能因为 是管理员明确地要这么做,也可能是这个 servlet 已经空闲的很长一段时间。当第一次请求 一个 jsp(假设这个 jsp 还没有被初次编译),JSP 引擎将转译这个 jsp 为一个页面实现类, 切实一个标准的 servlet 类。只要这个页面实现类 servlet 一创建,它将被 JSP 引擎编译为一 个 class 文件、 备用。 每当客户请求这个 jsp 时, 编译器将首先会检查这个 jsp 是否被修改了。 如果是的话,JSP 引擎将把它重新转译,以确保给客户端的响应是最新的 jsp 页面实现所生 成的。以.ear, .war, .rar 形式的企业应用程序部署单元,也需要在需要的时候或配置策略改变 时,被加载或重新加载。对所有的情况来说,都有可能被加载、移除以及重新加载,除非 我们已经控制了应用程序服务器的类加载策略。这通过扩展类加载器可以做到,因为它能 执行在它范围内的代码。  Brett Peterson  已 经 在 发 表 在  TheServerSide.com  的  "  Understanding J2EE Application Server Class Loading Architectures  "  上 给 出 了  J2EE application server 的类加载模式的解释。 总结 这篇文章讲述了怎样把类加载到 JVM 并唯一标识,和具有相同类名和包名时的一些限制。 因为没有直接的类版本结构,如果我们想按我们自己得到想法来加载类时,我们不得不使 用客户类加载器来扩展实现。许多 J2EE 应用程序服务器有“热部署”的能力,能够使我们以 一个新的类定义来重新加载应用程序,而不需要关闭服务器。这些应用程序服务器利用了 客户类加载器。尽管我们不使用应用程序服务器,我们可以创建和使用客户类加载器来更 好的控制 java 应用程序的类加载。 Ted Neward 的书Server­Based Java Programming  非常好的描述了 java 类加载的详细细节,并告诉了我们一些在 J2EE APIs 中没有提到的东 西以及怎样更好的去使用它们。 参考  Sample code for this article  JDK 1.5 API Docs  The Java language specification  "Understanding Extension Class Loading " in the Java tutorial  "Inside Class Loaders" from ONJava  "Inside Class Loaders: Debugging" from ONJava  "What version is your Java code?" from JavaWorld  "Understanding J2EE Applicatio ClassNotFoundException 和 NoClassDefFoundErr 异常-程序调试阶段常见的两个异常 2009-02-12 19:51 在读这篇文章之前,你最好了解一下 Java 的 Exception 机制。 也许你在开发的过程中经常地见到 ClassNotFoundException 和 NoClassDefFoundErr 这两 个异常,每每看到之后,都会一概而论的是类没有找到,但有些时候见到他们的时候又有 些疑惑(至少我是这样),为什么 Java 要用两个异常来表示类定义没有找到呢?他们之间 有什么区别呢? 正巧今天我又碰到了这个问题,顺便仔细研究了一下这两个异常的区别。 首先: ClassNotFoundException 直接继承与 Exception,它是一个 checked 的异常。 NoClassDefFoundErr 继承自 Error->LinkageError ,它是一个 unchecked 的异常。 下面让我们看一下两个异常在 API 文档中的说明。 ClassNotFoundException: 当应用尝试用字符串名称通过下面的方法装载一个类,这个类的定义却没有找到时会抛出 的异常。 ■Class.forName ■ClassLoader.findSystemClass ■ClassLoader.loadClass NoClassDefFoundErr: 当 JVM 或者 ClassLoader 实例尝试装载一个类的定义(这通常是一个方法调用或者 new 表 达式创建一个实例过程的一部分),而这个类定义并没有找到时所抛出的错误。 当编译的时候可以找到这个类的定义,但是以后这个类不再存在。 这比较显而易见了吧,读好文档是很重要的事情。这里我就说一下我对这两个类的区别的 理解。 ClassNotFoundException 异常只出现在你的应用程序主动的装载类的过程中,这个异常很 多时候出现在,我们的应用框架在初始化、或者运行中动态装载已配置的类的过程中。这 种情况下,我们应该首先检查我们的配置或者参数是否错误,是否企图装载一个并不存在 的类,如果配置没有错误,我们就应该查看 Classpath 是否配置错误而导致 ClassLoader 无法找到这个类,也应该检查要装载的类是否在一个 jar 包中,而我们在引入这个 jar 包 的过程中是否有遗漏或错误(这里 jar 包的版本也是一个需要格外注意的问题,很多时候 混乱的 jar 包版本会造成太多的麻烦)。 NoClassDefFoundErr 异常一般出现在我们编译环境和运行环境不一致的情况下,就是说我 们有可能在编译过后更改了 Classpath 或者 jar 包所以导致在运行的过程中 JVM 或者 ClassLoader 无法找到这个类的定义(我曾经在编译后作了一次 jar 包的清理,然后应用就 送给了我一个这样的礼物)。 我们经常用 SDK 开发应用,开发的过程中要引入很多 jar 包,有些 SDK 也会设定自己的 Classpath。编译过程结束后在运行的过程中就要将已开发的应用和所有引入的 jar 包拷贝 到应用服务器的相应目录下才可以运行, 而应用服务器使用的 Classpath 也很有可能与 SDK 的不同,在这个过程中就有很大的几率造成双方环境不一致。所以很多开发者就会遇到在 SDK 中 可 以 编 译 , 运 行 也 没 有 问 题 , 但 是 同 样 的 程 序 放 到 应 用 服 务 器 上 就 出 现 NoClassDefFoundErr 这个异常这种情况,这是让初学者很头疼的一个问题。 以上就是我对这两个异常的一点个人理解,希望对各位开发者有所帮助,可以让各位开发 者在以后的开发过程中能够更快的找到问题所在。祝开发顺利!
还剩26页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 3 人已下载

下载pdf

pdf贡献者

jackey2010

贡献于2010-11-07

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf