public class Strange1 {
public static void main(String[] args) {
try {
Missing m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
public class Strange2 {
public static void main(String[] args) {
Missing m;
try {
m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
class Missing {
Missing() { }
}
如果您在删除Missing.class
后运行Strange1和Strange2,Strange1将抛出NoClassDefFoundError;
但是Strange2会打印得到它!
任何人都可以解释一下吗?感谢。
更新
Strange1
的java字节码:
0 new info.liuxuan.test.Missing [16]
3 dup
4 invokespecial info.liuxuan.test.Missing() [18]
7 astore_1 [m]
8 goto 20
11 astore_1 [ex]
12 getstatic java.lang.System.out : java.io.PrintStream [19]
15 ldc <String "Got it!"> [25]
17 invokevirtual java.io.PrintStream.println(java.lang.String) : void [27]
20 return
Exception Table:
[pc: 0, pc: 8] -> 11 when : java.lang.NoClassDefFoundError
Line numbers:
[pc: 0, line: 14]
[pc: 11, line: 15]
[pc: 12, line: 16]
[pc: 20, line: 18]
Local variable table:
[pc: 0, pc: 21] local: args index: 0 type: java.lang.String[]
[pc: 8, pc: 11] local: m index: 1 type: info.liuxuan.test.Missing
[pc: 12, pc: 20] local: ex index: 1 type: java.lang.NoClassDefFoundError
Strange2
的java字节码:
0 new info.liuxuan.test.Missing [16]
3 dup
4 invokespecial info.liuxuan.test.Missing() [18]
7 astore_1 [m]
8 goto 20
11 astore_2 [ex]
12 getstatic java.lang.System.out : java.io.PrintStream [19]
15 ldc <String "Got it!"> [25]
17 invokevirtual java.io.PrintStream.println(java.lang.String) : void [27]
20 return
Exception Table:
[pc: 0, pc: 8] -> 11 when : java.lang.NoClassDefFoundError
Line numbers:
[pc: 0, line: 15]
[pc: 11, line: 16]
[pc: 12, line: 17]
[pc: 20, line: 19]
Local variable table:
[pc: 0, pc: 21] local: args index: 0 type: java.lang.String[]
[pc: 8, pc: 11] local: m index: 1 type: info.liuxuan.test.Missing
[pc: 12, pc: 20] local: ex index: 2 type: java.lang.NoClassDefFoundError
只有一个地方不同:
11 astore_1 [ex]
和
11 astore_2 [ex]
再次更新:
每个人都可以在日食中尝试。
答案 0 :(得分:2)
在说什么之前,我怀疑这个代码甚至无法编译。因为当编译器找不到一个类(因为它被删除)。尝试使用javac
命令编译时可能会出错。如果是这样的情况,它很正常,绝不是很奇怪。
让我添加另一个点..检查你的导入,包含缺少类。如果它在那里然后删除它。并告诉我们发生了什么。
答案 1 :(得分:1)
我创建了两个java文件。 Strange1.java包含Strange1和Missing类。 Strange2.java包含Strange2类。我删除了Missing.class。 我得到了“知道了!”来自两者。
请参阅以下详细信息:
manohar@manohar-natty:~$ java -version
java version "1.6.0_25"
Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
Java HotSpot(TM) Server VM (build 20.0-b11, mixed mode)
manohar@manohar-natty:~$ gedit Strange1.java
manohar@manohar-natty:~$ gedit Strange2.java
manohar@manohar-natty:~$ javac Strange1.java
manohar@manohar-natty:~$ javac Strange2.java
manohar@manohar-natty:~$ java Strange1
manohar@manohar-natty:~$ java Strange2
manohar@manohar-natty:~$ rm Missing.class
manohar@manohar-natty:~$ java Strange1
Got it!
manohar@manohar-natty:~$ java Strange2
Got it!
我在Ubuntu 11.04 linux机器上执行它。
所以它可能是你正在使用的java版本。
答案 2 :(得分:0)
只要对缺失的类进行第一次引用(声明或创建实例),就会抛出NoClassDefFoundError。现在,抛出错误或捕获它取决于你是否使用try-catch块作为第一次引用。
答案 3 :(得分:0)
这两个程序的行为取决于用于编译它们的 javac
版本,而不是用于运行已编译类的 java
版本。但是,使用相同的 javac
和 java
版本更容易。
我们将使用 J2SE 5.0 和 Java SE 6,因为这些是程序行为发生偏差的最早版本。
使用build 1.5.0_22-b03
:
$ jdk1.5.0_22/bin/javac {Strange1,Strange2,Missing}.java
$ rm Missing.class
$ jdk1.5.0_22/bin/java Strange1
Exception in thread "main" java.lang.NoClassDefFoundError: Missing
$ jdk1.5.0_22/bin/java Strange2
Got it!
$ jdk1.5.0_22/bin/javap -c Strange1
Compiled from "Strange1.java"
public class Strange1 extends java.lang.Object{
public Strange1();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2; //class Missing
3: dup
4: invokespecial #3; //Method Missing."<init>":()V
7: astore_1
8: goto 20
11: astore_1
12: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #6; //String Got it!
17: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
Exception table:
from to target type
0 8 11 Class java/lang/NoClassDefFoundError
}
Compiled from "Strange2.java"
public class Strange2 extends java.lang.Object{
public Strange2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2; //class Missing
3: dup
4: invokespecial #3; //Method Missing."<init>":()V
7: astore_1
8: goto 20
11: astore_2
12: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #6; //String Got it!
17: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
Exception table:
from to target type
0 8 11 Class java/lang/NoClassDefFoundError
}
使用build 1.6.0_45-b06
:
$ jdk1.6.0_45/bin/javac {Strange1,Strange2,Missing}.java
$ rm Missing.class
$ jdk1.6.0_45/bin/java Strange1
Got it!
$ jdk1.6.0_45/bin/java Strange2
Got it!
$ jdk1.6.0_45/bin/javap -c Strange1
<Same output as the corresponding command for J2SE 5.0>
$ jdk1.6.0_45/bin/javap -c Strange2
<Same output as the corresponding command for J2SE 5.0>
Strange1
和 Strange2
的字节码几乎相同,除了 catch 参数 ex
映射到 VM 局部变量。 Strange1
将其存储在 VM 变量 1 中,即 11: astore_1
。 Strange2
将其存储在 VM 变量 2 中,即 11: astore_2
。
在这两个类中,局部变量 m
都存储在 VM 变量 1 中。main
的两个版本也有一个合并点,来自两个不同代码路径的控制流在这里汇聚。合并点是20: return
。可以通过正常完成 try
块(即 8: goto 20
)或通过完成 catch
块并从指令 17 失败来达到它。
在 J2SE 5.0 类 Strange1
的验证过程中,合并点的存在会导致异常,但不会导致类 Strange2
的验证。
JLS, Java SE 6 Edition - Chapter 12 - Execution 指定在程序执行期间发生的活动:
<块引用>Java 虚拟机通过加载指定的类然后调用该指定类中的方法 main 来启动。第 12.1 节概述了执行 main 所涉及的加载、链接和初始化步骤,作为本章概念的介绍。其他部分详细说明了加载(第 12.2 节)、链接(第 12.3 节)和初始化(第 12.4 节)。
JLS, Java SE 6 Edition - Section 12.3 - Linking of Classes and Interfaces 指定链接中涉及的第一个活动是验证。
JVMS, Java SE 7 Edition - Section 4.10 - Verification of class Files 指定 VM 可用于验证的两种策略:
<块引用>Java 虚拟机实现可能有两种策略 用于验证:
必须使用类型检查验证来验证版本号大于或等于 50.0 的类文件。
所有 Java 虚拟机实现都必须支持通过类型推断进行验证,除了那些符合 Java ME CLDC 和 Java Card 配置文件,以验证类文件 版本号小于50.0。
支持 Java ME CLDC 和 Java Card 配置文件的 Java 虚拟机实现的验证受其控制 各自的规格。
(Verification by type checking was added to JVMS, Second Edition for Java SE 6 和 JVMS,Java SE 7 版以更易于访问的方式合并了这些更改。)
合并两个局部变量数组状态,对应的局部变量对 变量进行比较。如果两种类型不相同,则 除非两者都包含参考值,否则验证者会记录 局部变量包含不可用的值。如果双方都 局部变量包含引用值,合并状态包含一个 对两者的第一个公共超类的实例的引用 类型。 JVMS, Second Edition - Section 4.9.2 - The Bytecode Verifier
当从 Strange1.main
中的指令 8 到达指令 20 时,VM 变量 1 包含类 Missing
的实例。当从指令 17 到达时,它包含类 NoClassDefFoundError
的一个实例。
因为Missing.class
已被删除,验证器无法加载它以确定第一个公共超类,并抛出一个NoClassDefFoundError
。请注意,没有为未捕获的异常打印堆栈跟踪,因为它是在验证期间、类初始化之前和 main
开始执行之前很久抛出的。
(我已尽力尽可能准确地遵守规则。但是,它们对我来说是复杂、密集和新的。因此,如果您发现任何错误,请随时纠正它们。如果您能总结一下规则,那也很棒。)
由于 StackMapTable
属性,验证者不必像在其他策略中那样计算第一个公共超类来合并两个 VM 变量 1 的类型。此外,由于常量池和规则的工作原理,无需实际加载类 Missing
、NoClassDefFoundError
或除 Strange1
之外的任何其他类进行验证。
因此,在验证过程中没有例外。如果您修改 catch
块以打印出异常的堆栈跟踪,您将看到在执行 Strange1.main
期间抛出的异常具有正确的堆栈跟踪:
# Modify Strange1.main's catch block to print out the exception's stack trace
$ jdk1.6.0_45/bin/javac {Strange1,Missing}.java
$ rm Missing.class
$ jdk1.6.0_45/bin/java Strange1
java.lang.NoClassDefFoundError: Missing
at Strange1.main(Strange1.java:4)
Caused by: java.lang.ClassNotFoundException: Missing
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... 1 more
Got it!
StackMapTable
的 Strange1
:
$ jdk1.6.0_45/bin/javap -verbose Strange1 | tail
line 10: 20
StackMapTable: number_of_entries = 2
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/NoClassDefFoundError ]
frame_type = 8 /* same */
}
让我们通过规则(作为验证者)来证明 Strange1.main
是类型安全的。 Strange1.main
的每条指令都经过验证是类型安全的并且满足所有适用的(异常)处理程序:
methodWithCodeIsTypeSafe(Strange1Class, MainMethod) :-
parseCodeAttribute(Strange1Class, MainMethod, FrameSize, MaxStack,
ParsedCode, Handlers, StackMap),
mergeStackMapAndCode(StackMap, ParsedCode, MergedCode),
methodInitialStackFrame(Strange1Class, MainMethod, FrameSize, StackFrame, ReturnType),
Environment = environment(Strange1Class, MainMethod, ReturnType, MergedCode,
MaxStack, Handlers),
handlersAreLegal(Environment),
mergedCodeIsTypeSafe(Environment, MergedCode, StackFrame).
mergedCodeIsTypeSafe(Environment, [instruction(Offset, Parse) | MoreCode],
frame(Locals, OperandStack, Flags)) :-
instructionIsTypeSafe(Parse, Environment, Offset,
frame(Locals, OperandStack, Flags),
NextStackFrame, ExceptionStackFrame),
instructionSatisfiesHandlers(Environment, Offset, ExceptionStackFrame),
mergedCodeIsTypeSafe(Environment, MoreCode, NextStackFrame).
mergedCodeIsTypeSafe(Environment, [stackMap(Offset, MapFrame) | MoreCode],
afterGoto) :-
mergedCodeIsTypeSafe(Environment, MoreCode, MapFrame).
instructionSatisfiesHandlers(Environment, Offset, ExceptionStackFrame) :-
exceptionHandlers(Environment, Handlers),
sublist(isApplicableHandler(Offset), Handlers, ApplicableHandlers),
checklist(instructionSatisfiesHandler(Environment, ExceptionStackFrame),
ApplicableHandlers).
instructionSatisfiesHandler(Environment, StackFrame, Handler) :-
...
/* The stack consists of just the exception. */
StackFrame = frame(Locals, _, Flags),
ExcStackFrame = frame(Locals, [ ExceptionClass ], Flags),
operandStackHasLegalLength(Environment, ExcStackFrame),
targetIsTypeSafe(Environment, ExcStackFrame, Target).
targetIsTypeSafe(Environment, StackFrame, Target) :-
offsetStackFrame(Environment, Target, Frame),
frameIsAssignable(StackFrame, Frame).
指令 0-7 是类型安全的,因为 instructionIsTypeSafe
是 true
,frameIsAssignable(Environment, frame(Locals, [ NoClassDefFoundErrorClass ], Flags), frame(Locals, [ NoClassDefFoundErrorClass ], Flags))
是 true
,这要归功于(例外)的第一个堆栈映射帧指令 19 (= 75 - 64) 处的处理程序目标:
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/NoClassDefFoundError ]
由于常量池和未列出的相应规则(因为它们不构成 StackMapTable
属性),说明 12-17 是类型安全的。
还有 3 条指令可以证明它们的类型安全性:
8: goto 20
是类型安全的,这要归功于指令 20 (= 75 - 64 + 8 + 1 = 19 + 8 + 1) 处的目标的第二个堆栈映射帧,指示验证器那里的堆栈帧存在与指令 8 处的前一个堆栈帧相同:frame_type = 8 /* same */
instructionIsTypeSafe(goto(Target), Environment, _Offset, StackFrame,
afterGoto, ExceptionStackFrame) :-
targetIsTypeSafe(Environment, StackFrame, Target),
exceptionStackFrame(StackFrame, ExceptionStackFrame).
11: astore_1
是类型安全的,因为 store 是类型安全的,因为它可以将 NoClassDefFoundError
的子类型 reference
从堆栈中弹出(再次感谢第一个堆栈映射帧),然后将该类型合法地分配给局部变量 1,即 Locals = [arrayOf(String), class(Missing, Lm)] -> NewLocals = [arrayOf(String), class(NoClassDefFoundError, Ln)]
。An astore instruction with operand Index is type safe and yields an outgoing type state NextStackFrame, if a store instruction with operand Index and type reference is type safe and yields an outgoing type state NextStackFrame.
instructionIsTypeSafe(astore(Index), Environment, _Offset, StackFrame,
NextStackFrame, ExceptionStackFrame) :-
storeIsTypeSafe(Environment, Index, reference, StackFrame, NextStackFrame),
exceptionStackFrame(StackFrame, ExceptionStackFrame).
More precisely, the store is type safe if one can pop a type ActualType that "matches" Type (that is, is a subtype of Type) off the operand stack (§4.10.1.4), and then legally assign that type the local variable LIndex.
storeIsTypeSafe(_Environment, Index, Type,
frame(Locals, OperandStack, Flags),
frame(NextLocals, NextOperandStack, Flags)) :-
popMatchingType(OperandStack, Type, NextOperandStack, ActualType),
modifyLocalVariable(Index, ActualType, Locals, NextLocals).
20: return
是类型安全的,因为 Strange1.main
声明了 void
返回类型,并且封闭方法不是 <init>
方法:A return instruction is type safe if the enclosing method declares a void return type, and either:
- The enclosing method is not an <init> method, or
- this has already been completely initialized at the point where the instruction occurs.
instructionIsTypeSafe(return, Environment, _Offset, StackFrame,
afterGoto, ExceptionStackFrame) :-
thisMethodReturnType(Environment, void),
StackFrame = frame(_Locals, _OperandStack, Flags),
notMember(flagThisUninit, Flags),
exceptionStackFrame(StackFrame, ExceptionStackFrame).