ClassCastException是JVM在检测到两个类型间转换不兼容时引发的运行时异常。此类错误通常会终止用户请求。在执行任何子系统的应用程序代码时都有可能发生ClassCastException异常。通过转换,可以指示Java编译器将给定类型的变量作为另一种变量来处理。对基础类型和用户定义类型都可以转换。Java语言规范定义了允许的转换,其中大多数可在编译时进行验证。不过,某些转换还需要运行时验证。如果在此运行时验证过程中检测到不兼容,JVM就会引发ClassCastException异常。例如:
Fruit f;
Apple a = (Apple)f;
当出现下列情况时,就会引发ClassCastException异常:
1. Fruit和Apple类不兼容。当应用程序代码尝试将某一对象转换为某一子类时,如果该对象并非该子类的实例,JVM就会抛出ClassCastException异常。
2. Fruit和Apple类兼容,但加载时使用了不同的ClassLoader。这是这种异常发生最常见的原因。在这里,需要了解一下什么是ClassLoader?
ClassLoader
ClassLoader是允许JVM查找和加载类的一种Java类。JVM有内置的ClassLoader。不过,应用程序可以定义自定义的ClassLoader。应用程序定义新的ClassLoader通常出于以下两种原因:
1. 自定义和扩展JVM加载类的方式。例如,增加对新的类库(网络、加密文件等)的支持。
2. 划分JVM名称空间,避免名称冲突。例如,可以利用划分技术同时运行同一应用程序的多个版本(基于空间的划分)。此项技术在应用服务器(如WebLogic Server)内的另一个重要用途是启用应用程序热重新部署,即在不重新启动JVM的情况下启动应用程序的新版本(基于时间的划分)。
ClassLoader按层级方式进行组织。除系统BootClassLoader外,其它ClassLoader都必须有父ClassLoader。
在理解类加载的时候,需要注意以下几点:
1. 永远无法在同一ClassLoader中重新加载类。“热重新部署”需要使用新的ClassLoader。每个类对其ClassLoader的引用都是不可变的:this.getClass().getClassLoader()。
2. 在加载类之前,ClassLoader始终会先询问其父ClassLoader(委托模型)。这意味着将永远无法重写“核心”类。
3. 同级ClassLoader间互不了解。
4. 由不同ClassLoader加载的同一类文件也会被视为不同的类,即便每个字节都完全相同。这是ClassCastException的一个典型原因。
5. 可以使用Thread.setContextClassLoader(a)将ClassLoader连接到线程的上下文。
基于以上的基本原理,可以加深大家对ClassCastException的理解,和在碰到问题时提供一种解决问题的思路。
ClassCastException and ClassLoader Puzzle
2/21/2013
PIERRE-HUGUES CHARBONNEAU
2 COMMENTS
The following question and puzzle will test your knowledge on Java class loaders and more precisely on one of the Java language specifications. It will also help you better troubleshoot problems such asjava.lang.NoClassDefFoundError.
I highly suggest that you do not look at the explanation and solution until you review the code and come up with your own explanation.
You can download the Java program source code and binaries (compiled with JDK 1.7) here. In order to run the program, simply use the following command:
<JDK 1.7 HOME>\bin\java -classpath MainProgram.jar org.ph.javaee.training9.ChildFirstCLPuzzle
** Make sure that you also download the 3 JAR files below before you run the program.
- MainProgram.jar contains the main program along with super classProgrammingLanguage.
- ProgrammingLanguage.jar contains the super class ProgrammingLanguage.
- JavaLanguage.jar contains the implementation class JavaLanguage, which extendsProgrammingLanguage.
Question (puzzle)
Review closely the program source and packaging along with the diagram below reflecting the class loader delegation model used for this program.
Why can’t we cast (ChildFirstCLPuzzle.java @line 53) the Object javaLanguageInstance of type
JavaLanguage
, into
ProgrammingLanguage
?
...............................
// Finally, cast the object instance into ProgrammingLanguage
/** Question: why is the following code failing with ClassCastException given the fact JavaLanguage is indeed a ProgrammingLanguage?? **/
ProgrammingLanguage programmingLanguage = (ProgrammingLanguage)javaLanguageInstance;
...............................
Propose a solution to allow the above cast to succeed without changing the original source code. Hint: look again at the class loader delegation model, packaging and diagram.
Answer & solution
The Java program is attempting to demonstrate a Java language specification rule related to class loaders: two classes loaded by different class loaders are considered to be distinct and hence incompatible.
If you review closely the program source, packaging and diagram, you will realize the following facts:
- Our main program is loaded to the parent class loader e.g. $AppClassLoader.
- The super class ProgrammingLanguage is also loaded to the parent class loader since it is referenced by our main program at line 53.
- The implementation class JavaLanguage is loaded to our child class loader e.g. ChildFirstClassLoader which is following a “child first” delegation model.
- Finally, the super class ProgrammingLanguage is also loaded to our child class loader.
The key point to understand is that the super class is loaded by 2 different class loaders. As per Java language specification, two classes loaded by different class loaders are considered to bedistinct and hence incompatible. This means that ProgrammingLanguage loaded from the “child firs” class loader is different and not compatible with ProgrammingLanguage loaded from the parent classloader. This is why the cast attempted at line 53 failed with the error below:
ChildFirstCLPuzzle execution failed with ERROR: java.lang.ClassCastException: org.ph.javaee.training9.JavaLanguage cannot be cast to org.ph.javaee.training9.ProgrammingLanguage
Now how can we fix this problem without actually changing the source code? Well please keep in mind that we are using a “child first” delegation model here. This is why we end up with 2 versions of the same ProgrammingLanguage class. The solution can be visualized as per below.
In order to fix this problem, we can simply ensure that we have only one version of ProgrammingLanguage loaded. Since our main program is referencing ProgrammingLanguage directly, the solution is to remove the ProgrammingLanguage.jar file from the child class loader. This will force the child class loader to look for the class from the
parent
class loader, problem solved! In order to test the solution, simply remove the ProgrammingLanguage.jar from your testing folder and re-run the program.
I hope you appreciated this puzzle related to “child first” class loader delegation model and class loader rules. This understanding is especially important when you are dealing with complex Java EE deployments involving many class loaders, exposing you to this type of problems at runtime.
Please do not hesitate to post any comment or question on this puzzle.
ClassCastException深入分析