java.lang.NoClassDefFoundError原因 以及如何解决

哈哈 阅读:4553 2021-09-05 18:13:08 评论:0

ClassNotfoundException
java开发中经常遇到java.lang.ClassNotfoundException异常,ClassNotfoundException异常一般就是编译时找不到类,Console台就会输出异常信息。一般情况下,我们都会rebuild或者clean一下工程,让项目重新编译一遍。

两个异常的区别
不过一遇到NoClassDefFoundError,我们有时也容易和ClassNotfoundException异常混淆。虽然NoClassDefFoundError和ClassNotfoundException异常出现原因类似,都是因为编译加载不到类导致的,但是却有本质的区别。

两者的本质区别就是: 
ClassNotfoundException时在编译时JVM加载不到类或者找不到类导致的; 
而NoClassDefError是在运行时JVM加载不到类或者找不到类

NoClassDefFoundError错误发生原因
为什么会发生NoClassDefFoundError错误? 
其实就是和Java虚拟机的工作原理有关了,下面简单介绍一下JVM的类加载机制

类加载器三个机制:委托、单一性、可见性

委托:指加载一个类的请求交给父类加载器,若父类加载器不可以找到或者加载到,再加载这个类 
单一性:指子类加载器不会再次加载父类加载器已经加载过的类 
可见性:子类加载器可以看见父类加载器加载的所有类,而父类加载器不可以看到子类加载器加载的类

JVM的类加载机制的委托行机制,决定了类加载器只加载一次,子类加载器不会再加载父类加载器已经加载过的类

所有在一些特定条件下就会出现编译时可以加载到类,运行时不可以加载到类,这时候就会出现java.lang.NoClassDefFoundError异常

NoClassDefFoundError可能出现的情况
目前有发现几种情况 
1、一种情况就是因为静态变量加载不到原因 
2、工程里没有将jar添加到classpath,maven项目的,需要根据项目情况排查
--------------------- 
 

如何解决?

maven打可执行的jar包以及classpath设置

一  将maven构建的工程打成jar包,并使用脚本做定时任务

1.首先将maven搭建的工程打成jar

需要在将该工程所支持的类库打在一个包中(默认情况下,maven在做mvn pakage时,只是将项目编译打包到一个jar中,其他的类库则需要引用才行)。

在pom.xml中加入以下插件

 
 
  1. <plugin> 
  2.  
  3.     <artifactId>maven-assembly-plugin</artifactId> 
  4.  
  5.     <configuration> 
  6.  
  7.         <appendAssemblyId>false</appendAssemblyId> 
  8.  
  9.         <descriptorRefs> 
  10.  
  11.             <descriptorRef>jar-with-dependencies</descriptorRef> 
  12.  
  13.         </descriptorRefs> 
  14.  
  15.         <archive> 
  16.  
  17.             <manifest> 
  18.  
  19.                 <mainClass>cn.vsp.TestMain</mainClass> 
  20.  
  21.             </manifest> 
  22.  
  23.         </archive> 
  24.  
  25.     </configuration> 
  26.  
  27.     <executions> 
  28.  
  29.         <execution> 
  30.  
  31.             <id>make-assembly</id> 
  32.  
  33.             <phase>package</phase> 
  34.  
  35.             <goals> 
  36.  
  37.                 <goal>assembly</goal> 
  38.  
  39.             </goals> 
  40.  
  41.         </execution> 
  42.  
  43.     </executions> 
  44.  
  45. </plugin> 

上述代码中在<mainClass></mainClass>之间是填写程序的入口类,即含main方法的类

2.编辑完上述插件后,再执行

mvn assembly:assembly

这是就会在target目录下生成   *.jar 文件

3.运行jar文件

java -jar *.jar

4.做定时任务

如果是在linux系统中做定时任务:

第一种方式:可以写一个shell脚本直接运行该jar文件,shell脚本的编写可以参考我的linux svn完全备份和定时备份博文

第二种方式:

sudo crontab -l

*/1 * * * * *  sudo /usr/bin/java -jar  *.jar 

上述代码为每分钟执行一次,路径修改为相应的路径。

 

二 maven使用制定的java版本,来编译

可以避免不支持泛型,请使用 -source 5 或更高版本以启用泛型)” 这种问题

 在项目的pom.xml中,添加:

 
 
  1. <build> 
  2. <plugins> 
  3. <plugin> 
  4. <groupId>org.apache.maven.plugins</groupId> 
  5. <artifactId>maven-compiler-plugin</artifactId> 
  6. <configuration> 
  7. <source>1.5</source> 
  8. <target>1.5</target> 
  9. </configuration> 
  10. </plugin> 
  11. </plugins> 
  12. </build> 

 

三 设置classpath来运行jar

一、背景知识

自JDK 1.2以后,JVM采用了委托(delegate)模式来载入class.采用这种设计的原因可以参考http://java.sun.com/docs/books/tutorial/ext/basics/load.html

归纳来讲:是基于JVM sandbox(沙盒)安装模型上提供应用层的可定制的安全机制.

二、Java虚拟机(JVM)寻找Class的顺序

2.1 Bootstrap classes

属于Java 平台核心的class,比如java.lang.String等.及rt.jar等重要的核心级别的class.这是由JVM Bootstrap class loader来载入的.一般是放置在{java_home}\jre\lib目录下

2.2 Extension classes

基于Java扩展机制,用来扩展Java核心功能模块.比如Java串口通讯模块comm.jar.一般放置在{Java_home}\jre\lib\ext目录下

2.3 User classes

开发人员或其他第三方开发的Java程序包.通过命令行的-classpath或-cp,或者通过设置CLASSPATH环境变量来引用.JVM通过放置在{java_home}\lib\tools.jar 来寻找和调用用户级的class.常用的javac也是通过调用tools.jar来寻找用户制定的编译Java源程序.这样就引出了User class路径搜索的顺序或优先级别的问题.

2.3.1 缺省值:调用Java或javawa的当前路径(.),是开发的class所存在的当前目录

2.3.2 CLASSPATH环境变量设置的路径.如果设置了CLASSPATH,则CLASSPATH的值会覆盖缺省值

2.3.3 执行Java的命令行-classpath或-cp的值,如果制定了这两个命令行参数之一,它的值会覆盖环境变量CLASSPATH的值

2.3.4 -jar 选项:如果通过java –jar 来运行一个可执行的jar包,这当前jar包会覆盖上面所有的值.换句话说,-jar 后面所跟的jar包的优先级别最高,如果指定了-jar选项,所有环境变量和命令行制定的搜索路径都将被忽略.JVM APPClassloader将只会以jar包为搜索范围.有关可执行jar有许多相关的安全方面的描述,可以参考http://java.sun.com/docs/books/tutorial/jar/ 来全面了解.

这也是为什么应用程序打包成可执行的jar包后,不能引用第三方jar包的原因.

三、解决方案.

首先我们简要归纳一下,为了方便理解,简化一下上面描述的架构,

Java定义了三种级别的class,分别为BootStrap class,Extend Class,User class.

其中User class限制规则最复杂.在User class中可执行的jar包,更包含独立安全规则.所以解决方案基于Java三种不同级别的class扩展机制,有三种不同的方案.

3.1 BootStrap class扩展方案

Java 命令行提供了如何扩展bootStrap 级别class的简单方法.

-Xbootclasspath:基本核心的Java class 搜索路径.不常用,否则要重新写所有Java 核心class

-Xbootclasspath/a: 后缀在核心class搜索路径后面.常用.

-Xbootclasspath/p:前缀在核心class搜索路径前面.不常用,避免引起不必要的冲突.

语法如下:

java –Xbootclasspath/a:/path/myclass/account.jar: -jar yourself.jar(Unix用:号隔开)

java –Xbootclasspath:/d:/myclass/account.jar; -jar yourself.jar(Window用;号隔开)

3.2 extend class 扩展方案

Java exten class 存放在{Java_home}\jre\lib\ext 目录下.当调用Java时,对扩展class路径的搜索是自动的.总会搜索的.这样,解决的方案就很简单了,将所有要使用的第三方的jar包都复制到ext 目录下.

3.3 User class扩展方案

当使用-jar执行可执行Jar包时,JVM将Jar包所在目录设置为codebase目录,所有的class搜索都在这个目录下开始.所以如果使用了其他第三方的jar包,一个比较可以接受的可配置方案,就是利用jar包的Manifest扩展机制.步骤如下

1.将需要的第三方的jar包,复制在同可执行jar所在的目录或某个子目录下.

比如:jar 包在 d:\crm\luncher.jar 那么你可以把所有jar包复制到d:\crm目录下或d:\crm\lib 子目录下.

2.修改Manifest 文件

在Manifest.mf文件里加入如下行

Class-Path:classes12.jar lib/class12.jar

Class-Path 是可执行jar包运行依赖的关键词.详细内容可以参考http://java.sun.com/docs/books/tutorial/ext/index.html

3.4 User class扩展方案二

直接使用命令行模式来指定classpath以及要运行的main方法

windows:
java -cp "Test.jar;lib/*" my.package.MainClass

Under Linux:
java -cp "Test.jar:lib/*" my.package.MainClass


标签:java
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

搜索
关注我们

一个IT知识分享的公众号