Java 运行时发生 NoClassDefFoundError: Could not initialize class 的解决方法

最近遇到了如下问题:

在编译时没有异常的程序,在运行时抛出异常称 NoClassDefFoundError: Could not initialize class {类名}

根据 Java 官方文档,NoClassDefFoundError 是由于 JVM 或 ClassLoader 实例为了调用某个类的方法或 new 类的新的实例,而试图加载该类的定义时,却无法找到其定义,而抛出的异常。需要注意,对于抛出该异常的情况,试图找到的类的定义在编译时存在,只是在运行时不知所踪。

NoClassDefFoundError 总的来讲有两种情况,类文件不存在,或是类初始化错误。它们的错误信息不同。

  • 如果无法找到类文件,错误信息为 java.lang.NoClassDefFoundError: com/example/Foo
  • 如果类文件初始化错误,错误信息则如前文所提,为 NoClassDefFoundError: Could not initialize class {类名}。这通常由类的静态成员或静态初始化语句块引起。诸如 private static final MyClass val = new MyClass(); 或直接执行于 static {} 代码块中的语句抛出异常,都可能会引发 NoClassDefFoundError。针对第二种这种情况,可以在类的 static initializer 中增加 try catch 语句来捕获异常并输出日志,来了解具体错误内容。

此外还有一种相似但不同的错误,ClassNotFoundException。它通常由 Java 运行时在无法找到仅在运行过程中才需要加载的类时抛出。

可以从 Wikipedia 的 Java Classloader 条目进一步了解 classloader 的行为。

常见 Java 框架、库与工具简介

本文将不定期更新一些常见的 Java 框架与库,或是简单的介绍与感想。

框架

  • Spring – 文档与实例丰富;适合大中型系统
  • Play Framework – 轻量;适合 RESTful;源代码修改后无需重启编译器与服务器;内置 JSON 库
  • Dropwizard

服务器(Server)

IDE

标准 API

模板引擎(Template engine)

数据库(Database)

对象映射工具(Object mapping)

测试工具(Testing)

日志工具(Logging)

库(Library)

编译工具(Build tool)

其他工具

JVM 语言

参考链接:Awesome Java

初版条目的分类选取引用于 disc99 的博客文章《Javaを使うなら知っておきたい技術、フレームワーク、ライブラリ、ツールまとめ》。

Java 非访问修饰符简述

 

abstract

用于定义抽象方法。抽象方法仅具有签名(方法名、参数与返回值),需进一步覆盖使用。包含抽象方法的类称为抽象类,同样需要添加 abstract 修饰符。继承抽象类时必须覆盖其中所有的抽象方法。interface 指令也起到了抽象的功能,因此常省略重复使用 abstract。

 

static

用于定义静态成员(字段或方法)。静态成员无需实例化即可访问。

 

final

final 方法无法覆盖、final 字段无法再次赋值、final 类无法被继承。

 

transient

transient 对象不会被序列化。有时,为了将对象通过网络传输或存入数据库,需要将序列化(如将其转换为字节数组形式)。对于一些临时变量,或其他一些特殊原因,可通过 transient 修饰符是成员免于序列化。

 

volatile

多线程处理中,各线程的成员将各自分配缓存。volatile 修饰符强制所有线程中的成员共用同一缓存,共享相同的值。

 

synchronized

synchronized 修饰符使成员无法被多个线程同时访问,必须依次进行(同步处理)。如果方法分属不同实例,synchronized 将无法确保处理同步。

 

native

native 修饰符表示方法由非 Java 语言实现,因此,方法体将被省略。随着 Java 性能的改善,native 的应用范围也逐渐缩小。

 

strictfp

strictfp 修饰符要求程序严格遵循 IEEE 754 标准对浮点小数进行计算。此时,不同环境中浮点数运算的误差不复存在,但处理速度将有所下降。

Java 的内存大小与垃圾回收简述

一些 Java 程序在高负荷时性能会出现显著下降。一个可能的原因是没有设定恰当的内存大小(memory sizing),导致垃圾回收(Garbage collection,下称 GC)频繁执行。即使 CPU 满负荷运作,性能依然较差。

JVM 的内存分 Java 专用区域与 C Heap 两部分,其中前者又分为维护 Java 对象的 Java Heap 区域与维护类信息的 Permanent 区域。一些采用了分代收集机制(Generational Garbage Collection,或称 Generation Scavenging)的 GC 系统还将 Java Heap 进一步细分为 Eden、Survivor 与 Old 区域。Eden 维护生成的对象,GC 时未被回收的对象将被移入 Survivor,长期存在的对象进一步进入 Old 区域。

对于分代收集,GC 可分为 Full GC 与 Copy GC,其中,Copy GC 仅针对 New 区域(Eden+Survivor),Full GC 则针对所有区域。为防止 GC 过于频繁,我们需要设置合适的内存大小。常见的情况有以下这些:

Copy GC

  • Eden 区域空间不足

Full GC

  • Eden 区域空间不足时,Old 区域的可用内存大小不足以容纳 New 区域的已使用内存
  • New 区域与 Old 区域的可用内存空间不足
  • GC 后 Old 区域的剩余空间过低
  • GC 后 Old 区域的空间增加
  • Permanent 区域的可用空间不足

在程序执行过程中,Permanent 区域很少发生变化,因此我们因主要考虑 Full GC 的前两种情况。未避免 Old 区域的空间增加,建议将 Java Heap 的初始值与最大值设为同一值。

通常,Full GC 更为耗时,因此我们应主要考虑减少 Full GC,令 Copy GC 可以解决大部分回收。为此,Old 区域的内存大小是一个关键。

Old 区域的内存大小应与 Java 程序占用内存、Java 服务器占用内存,及 New 区域的内存大小,之和,相当。

举例来说,对于 Web 程序,事务处理占用的内存可以被立即释放,会话占用的内存则需要长期保存。因此后者应归为 Java 程序占用内存。

New 区域的内存大小也需要专门考虑,尤其是 Survivor 区域的大小十分重要。如果 Survivor 区域的内存大小不足,对象就需要被移动至 Old 区域。如果这些对象的生存周期不够长,就会进一步引发 Full GC。增加 Survivor 区域可以缓解该问题。然而,如果 Survivor 区域过大,Copy GC 的处理时间就会增加。因此,应当设置一个阀值,将经历 Copy GC 次数超过该值的对象移植 Old 区域。应尽可能避免 Survivor 区域已满,对象从 Eden 区域直接被移动至 Ole 区域的情况。为此,需要在 Copy GC 发生后检查 Survivor 区域是否被完全占用,以及移动至 Old 区域的对象年龄(生存次数)是否合适。如有可能,应为 Survivor 区域分配较大的内存大小,以适应需求。具体的内存大小估算方式,可以查找其他相关文章。

Eden 区域的内存大小将直接影响 Copy GC 的频率。通常,每发生 10-20 次 Copy GC 后执行一次 Full GC 较为合适。

Documentum 上传大尺寸文件

说明:

Documentum API (DFC) 提供了多种文件上传方法,如果需要上传的文件较大,则需要选择合适的方法。

其中,SetContent()及相关扩展方法需要使用 Byte 数组构造文件内容,因此会消耗较多内存,抛出 java.lang.OutOfMemoryError: Java heap space 异常。可以通过配置 JRE 的 Xms 参数(例:-vmargs -Xmx1024M 表示为 JVM 分配最少1024MB内存)增加内存分配以在一定程度上缓解该问题,但如果系统内存不足,将无法启动程序。

另一方面,SetFile()及相关扩展方法可以接收文件路径,无需将整个文件载入内存,但却需要耗费额外存储空间。应记得在完成处理后删除临时文件。

顺便一提,造成 heap space 不足通常可能有以下原因:线程过多、 java.io.File 类的 deleteOnExit() 方法维护的文件信息过多、大量 I/O 操作、java.util.zip 包的 Deflater 与 Inflater 类的构造函数在 end() 方法被调用前保存了大量信息、java.nio.ByteBuffer 类的 allocateDirect() 或 java.nio.channels.FileChannel类的 map() 方法生成了大量 Buffer 对象实例。

基于 Eclipse + Tomcat 的 Java Servlet Web 开发环境搭建与配置

2014年8月26日更新

本文应用的软件环境

  • Windows 8.1 Pro (64bit)
  • Eclipse IDE for Java EE Developers (Ver.4.4 Luna)
  • Tomcat 8.0.9
  • Java 7 Update 55 (64bit)

概览

  • 安装 Eclipse
  • 安装 Tomcat
  • 安装 JDK
  • 新建 HelloWorld Servlet

安装 Eclipse

从 http://www.eclipse.org/downloads/ 下载包含了 WTP (Web Tools Platform)的 Eclipse IDE for Java EE Developers。将下载得到的压缩包解压至任意路径。

安装 Tomcat

从 http://tomcat.apache.org/ 下载 Binary Distributions – Core – zip 文件。将下载得到的压缩包解压至任意路径。

安装 JDK

从 http://www.oracle.com/technetwork/java/javase/downloads/ 下载 JDK 并根据提示安装。

新建 HelloWorld Servlet

启动 Eclipse。

从菜单选择 File – New – Project – Web – Dynamic Web Project。

填写任意 [Project Name],此处采用 TomcatTest。选择 [New Runtime] – [Apache – Apache Tomcat v8.0] – [Create a new local server] – [Next]。

[Browse] 至 Tomcat 的解压路径。

在之后的 Web Module 页面勾选 [Generate web.xml deployment descriptor]。

点击 [Finish] 完成配置。

右键单击 workspace 中列出的项目 TomcatTest,选择 [Properties] – [Resource] – [Text file encoding] – [Other] – [UTF-8] 以避免字符编码问题。

单击 workspace 中列出的项目 TomcatTest,从菜单选择 [File] – [New] – [Other] – [Web] – [Servlet]。填写任意 [Class Name] (此处采用 HelloWorld)与 [Java Package]。

在新建的 HelloWorld.java 中的 doGet 方法中添加如下语句:

response.getWriter().write(“Hello, World!”);

在 Eclipse 的 Servers 标签页中右键单击先前创建的 Tomcat v8.0,选择 [Add and remove],将 TomcatTest 项目从 Available 栏移动至 Configured 栏。点击 [Finish]。

右键单击 Servers 标签页的 Tomcat v8.0,选择 [Start] 启动。

在浏览器中输入 http://localhost:8080/TomcatTest/HelloWorld 以确认 Tomcat 是否正常运行。页面应显示文字“Hello World!”。

对 TomcatTest 项目的 WebContent 文件夹右键单击,选择 [New] – [Other] – [Web] – [JSP File]。之后可以在浏览器中测试访问该页面。

Java 中线程的停止与启动

代码与范例:

说明:出于线程安全的考虑,Java 禁止使用 stop() 来结束线程。用于启动线程的 start() 只能执行一次,在执行 run() 之后即使再次调用 start(),run() 也不会被调用,必须重新调用 new()。而且根据虚拟机的具体实现,start() 与 run() 之间也可能有一定的延迟。

参考资料

Java 中获取随机数的方法

代码与范例:

说明:通过 Random 类及其相关方法获得随机变量。

Android 开发笔记

作为分类目录的一个补充,在这里按照功能类别对Android开发中的一点心得和笔记作一个索引。其中部分是根据自己在查找网络资料时找到的内容的整理与演绎,在此感谢所有那些无私分享经验的人们。

基本

控件使用

资源调用

声音和图像

用户界面

活动 线程 服务

设备控制

SQLite

Android Studio

其他