棘手的try-catch java代码

时间:2011-08-09 04:19:34

标签: java

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]

再次更新:

每个人都可以在日食中尝试。

4 个答案:

答案 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 版本。但是,使用相同的 javacjava 版本更容易。

我们将使用 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>

分析

Strange1Strange2 的字节码几乎相同,除了 catch 参数 ex 映射到 VM 局部变量。 Strange1 将其存储在 VM 变量 1 中,即 11: astore_1Strange2 将其存储在 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 版以更易于访问的方式合并了这些更改。)

通过类型推断验证(对于版本号小于 50.0 的类文件,即 Java SE 6)

<块引用>

合并两个局部变量数组状态,对应的局部变量对 变量进行比较。如果两种类型不相同,则 除非两者都包含参考值,否则验证者会记录 局部变量包含不可用的值。如果双方都 局部变量包含引用值,合并状态包含一个 对两者的第一个公共超类的实例的引用 类型。 JVMS, Second Edition - Section 4.9.2 - The Bytecode Verifier

当从 Strange1.main 中的指令 8 到达指令 20 时,VM 变量 1 包含类 Missing 的实例。当从指令 17 到达时,它包含类 NoClassDefFoundError 的一个实例。

因为Missing.class已被删除,验证器无法加载它以确定第一个公共超类,并抛出一个NoClassDefFoundError。请注意,没有为未捕获的异常打印堆栈跟踪,因为它是在验证期间、类初始化之前和 main 开始执行之前很久抛出的。

通过类型检查进行验证(对于版本号大于或等于 50.0 的类文件,即 Java SE 6)

(我已尽力尽可能准确地遵守规则。但是,它们对我来说是复杂、密集和新的。因此,如果您发现任何错误,请随时纠正它们。如果您能总结一下规则,那也很棒。)

由于 StackMapTable 属性,验证者不必像在其他策略中那样计算第一个公共超类来合并两个 VM 变量 1 的类型。此外,由于常量池和规则的工作原理,无需实际加载类 MissingNoClassDefFoundError 或除 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!

StackMapTableStrange1

$ 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 是类型安全的,因为 instructionIsTypeSafetrueframeIsAssignable(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).