Java Puzzlers里面的一个谜题,大家都给个解释,进者有分

killme2008 2006-05-15 09:52:39
请考虑下面两个类:
//Strange1.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!");
}
}
}

//Strange2.java
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!");
}
}
}
这两个类Strange1,Strange2都用到了下面的类:
//Missing.java
class Missing {
Missing() { }
}

如果你编译这3个类,然后在运行Strange1和Strange2之前删除Missing.class文件,你就会发现这两个程序的行为有所不同,其中一个抛出了一个未被捕获的NoClassDefFoundError异常,而另一个却打印了Got it!,到底是哪一个程序具有哪一种行为,你又如何去解释这种差异.

结果是:Strange1抛出了异常,Strange2打印了Got it!
书中的解释反而让我更糊涂,翻译的本来就不好,解释起来反而更糊涂了,大家都试试?发表下自己的看法.
...全文
942 33 打赏 收藏 转发到动态 举报
写回复
用AI写文章
33 条回复
切换为时间正序
请发表友善的回复…
发表回复
whereismyheart 2006-05-16
  • 打赏
  • 举报
回复
看看
yujie970730 2006-05-16
  • 打赏
  • 举报
回复
up
qinjiao 2006-05-16
  • 打赏
  • 举报
回复
看看
TroyorT 2006-05-16
  • 打赏
  • 举报
回复
有点迷糊!!
呵呵!
guojs_1 2006-05-16
  • 打赏
  • 举报
回复
不知道,进来看看!!
foxty 2006-05-15
  • 打赏
  • 举报
回复
呵呵:) 共同进步~
killme2008 2006-05-15
  • 打赏
  • 举报
回复
感谢大伙
我对Inside the JVM此书的理解又深入了一层
killme2008 2006-05-15
  • 打赏
  • 举报
回复
to TinyJimmy(Jimmy)
foxty(狐狸糊涂) 已经解释了,请注意看我上面贴的对于字节码的分析.在这个例子中问题性质不一样.此例的问题是局部变量的作用范围以及解析的时间问题
foxty 2006-05-15
  • 打赏
  • 举报
回复
to TinyJimmy(Jimmy) :

你看看上面的讨论,应该就知道这个puzzle的原因了。这个puzzle其实分析到最后跟载入关系不大,而是跟字节码验证有关西,因为在Strange1中Missng和异常对象占用了同一个局部变量区的地址,所以必须提前解析他们的公共超类,解析的时候就发现Missing类是不存在的,这样就在装载的时候抛出了异常。
foxty 2006-05-15
  • 打赏
  • 举报
回复
对的,你的这个说法我赞同,因为jvm在载入class的时候,只有常量池解析这个动作是延迟的,即使不延迟也是给用户看起来是延迟解析的。所以说Missing类开始是载入了,只是解析动作并未进行。

关于类的装载,在深入java虚拟机一书中有详细的介绍。
xingchen0yuxi 2006-05-15
  • 打赏
  • 举报
回复
看看
TinyJimmy 2006-05-15
  • 打赏
  • 举报
回复
怎么解释Strange1的错误没有进入main就抛出, 而Strange2在执行到m = new Missing()才抛出呢?
killme2008 2006-05-15
  • 打赏
  • 举报
回复
我的以上说法有误

装载在这里的概念应该是有3个基本动作:
1.通过该类型的全限定名,产生一个代表该类型的2进制数据流
2.解析此数据流为方法区的内部数据结构(方法表,常量池等)
3.创建代表此类型的class实例

因此TinyJimmy(Jimmy)不够准确,应当Missing不是不被装入,而是并没有被解析
解析是连接阶段的可选步骤,在此步骤才会把在常量池的符号引用解析为直接引用
killme2008 2006-05-15
  • 打赏
  • 举报
回复
to foxty(狐狸糊涂)
确实应该是这样,只因为Strange2中的m并非在try..catch块中声明,ex和m不是共享一个方法的局部变量区,那就不用验证公共超类了.我的理解和你一样.非常感谢您的参与,一开始还真弄不明白.

to TinyJimmy(Jimmy)
你的观点也没错,只是在连接的验证阶段,为了检查字节码的完整性和正确性,我可能提前装载某个类的.
foxty 2006-05-15
  • 打赏
  • 举报
回复
TinyJimmy(Jimmy) :

无论Missng是接口还是抽象类,只要你声明了Missing,在类得常量池中就会有这个引用,在进行常量池解析的时候,就会把这个对应的类、接口或抽象类都会载入,我这里说的载入是载入class文件。类的超类也自然会被先载入。
foxty 2006-05-15
  • 打赏
  • 举报
回复
应该是这样吧,在Strange1中,变量m和异常ex都属于局部变量,而且2个的作用域是完全不冲突的,所以虚拟机就让这两个局部变量的地址共享了,在载入的时候需要验证公共超类。

但是在Strange2中,由于m是一个方法内的局部变量,而ex是catch块中的局部变量,m的作用域包含了ex的作用域,所以2个局部变量不能共享一个方法的局部变量区,自然在载入的时候就不需要验证公共超类了。

不知道这个说法是否正确,也是根据你贴出来的文字分析得来得,如果有误还望大家指正。
TinyJimmy 2006-05-15
  • 打赏
  • 举报
回复
foxty(狐狸糊涂)
不是有Missing m这个声明,虚拟机就一定会将类Missing装载的. 如果Missing是一个接口或者抽象类, Missing不会被装入, 装入的只是Missing的实例对应的类而已. 现在的Strange2的Missing就是类似这样的情况
foxty 2006-05-15
  • 打赏
  • 举报
回复
我也看了下字节码,跟你现在想法大致一样,难道就是因为2个引用占用了同一个局部变量区的地址,所以在载入的时候需要验证公共超类?

killme2008 2006-05-15
  • 打赏
  • 举报
回复
分析Strange2的字节码,只有一句不同:
11:astore_2

那我上面的理解也错误,应该是Strange1把Missing的实例(还未初始化时是null)存储在变量1,而ClassNotFoundError异常的实例存储在变量2,两者的效验并不需要计算首个公共超类.这样的理解如何?

killme2008 2006-05-15
  • 打赏
  • 举报
回复
诚如foxty(狐狸糊涂) 所说,书中解释也是说Strange1在连接阶段的校验产生异常
按他的解释要分析下字节码,比如我用 javap -c Strange1看字节码:
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
ing;)V
20: return
Exception table:
from to target type
0 8 11 Class java/lang/NoClassDefFoundError


}
按照他的解释如下:
由于指令20(return)可以通过两种路径到达,因此效验器必须合并变量1中的类型(astore_1),两种类型通过计算他们的首个公共超类(first common superclass)而合并的,两个类的首个公共超类是它们所共有的最详细而精确的类.
在Strange1.main方法中,当从指令8到达指令20时,VM变量1的状态包含了一个Missing类的实例.,当从指令17到达20时,它包含了一个NoClassDefFoundError类的实例.为了计算首个公共超类,效验器必须加载Missing类以确定其超类.因为Missing.class被删除了,所以不能加载它,因而抛出NoClassDefFoundError异常.此异常在效验期间,初始化之前被抛出.

复述了一遍,我大概理解了为何Strange1抛出异常,那么,对于Strange2我是否可以这样理解,因为变量1首先是个Null实例,后来再有个NoClassDefFoundError类的实例,两者的首个公共超类都为Object,在此过程中(效验过程)并不需要加载Missing类,只有在初始化时才加载.我的理解正确不?谢谢
加载更多回复(12)

62,616

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧