Java Puzzlers里面的一个谜题和讨论

Posted on 2006-05-22 15:46 糊里糊涂 阅读(229) 评论(0)  编辑  收藏 所属分类: Java
主  题: Java Puzzlers里面的一个谜题,大家都给个解释,进者有分
作  者: killme2008 (为了生态平衡,请保护蛤蟆)
等  级:
信 誉 值: 96
所属社区: Java J2SE / 基础类
问题点数: 200
回复次数: 32
发表时间: 2006-5-15 9:52:40

请考虑下面两个类:
//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!
书中的解释反而让我更糊涂,翻译的本来就不好,解释起来反而更糊涂了,大家都试试?发表下自己的看法.

 

killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 10:17:15 得分: 0
 

没人来?继续迷糊中....希望有人解释下,谢谢

 

Top
huyc_fly() ( ) 信誉:100 2006-5-15 10:29:15 得分: 5
 

我的执行结果怎么都一样?
D:\workspace\csdn\bin\camel>java Strange1
Exception in thread "main" java.lang.NoClassDefFoundError: Strange1 (wrong name:
 camel/Strange1)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access$100(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)

D:\workspace\csdn\bin\camel>java Strange2
Exception in thread "main" java.lang.NoClassDefFoundError: Strange2 (wrong name:
 camel/Strange2)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access$100(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)


 

Top
huyc_fly() ( ) 信誉:100 2006-5-15 10:32:34 得分: 5
 

NoClassDefFoundError这个是继承于java.lang.Error,这个是运行时错误,好像不能在程序中用try{}catch{}来捕获的吧?
ps.我的jdk是5.0

 

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 10:44:25 得分: 0
 

不是,你肯定没在执行前删掉Missing.class吧??
你删掉Missing.class看看

Top
fronm(时间) ( ) 信誉:100 2006-5-15 10:52:04 得分: 5
 
进来看看!
Top
TinyJimmy(Jimmy) ( ) 信誉:105 2006-5-15 11:08:22 得分: 0
 

我想可从这个提示入手
Exception in thread "main" java.lang.NoClassDefFoundError: Missing

如果程序抛出的异常或错误,一般会把堆栈的错误信息打出来, 包括出现错误的行. 而这个错误很明细没有, 是VM抛出的错误.

Missing m = new Missing();

try {
  Missing m;
  m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
  System.out.println("Got it!");
}
虚拟机启动时, 需要装入Strange1这个类, 而在解析这个类的时候, 发现依Missing一定会被装入, 于是试图装入Missing, 发现没有, 于是报错.

Missing m;
try {
  m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
  System.out.println("Got it!");
}
而虚拟机处理上面一段时, 发现不一定被装入,
可能     m = new Missing(); //装入的是Missing
也可能是 m = new SonOfMissing();  //装入的是SonOfMissing
虚拟机自身盘点不明确, 所有Missing不认为是必须的, 所有没有装入这个类.

待程序执行到m = new Missing();的时候, 试图装入类, 这时候就爆出异常来了

 

Top
huyc_fly() ( ) 信誉:100 2006-5-15 11:08:38 得分: 0
 

我肯定是删除掉了Missing.class,不然不会产生异常的

 

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 11:11:46 得分: 0
 

却是如楼主所说,楼主把你看的解释贴出来吧,一起研究下。

估计是跟类的装载有关的,刚才小测试了一下,Strange1还未执行cinit方法就抛出异常了,看来应该是装载类时候的异常。而Strange2中却是都执行了。

一般来说,虚拟机中常量池的解析都是延迟解析的,所以应该不会是常量池解析的时候错,推断1中错误可能就是在验证类字节码的时候抛出的异常。难道是2种不同的写法,虚拟机对字节码的处理就不同了?

mark~~

Top
whulibo(JAVA 爱好者) ( ) 信誉:100 2006-5-15 11:13:12 得分: 5
 

接分~~~

Top
huyc_fly() ( ) 信誉:100 2006-5-15 11:17:32 得分: 0
 

不好意思,是我搞错了

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 11:18:47 得分: 0
 

TinyJimmy(Jimmy):

   你那个说法似乎也不妥:按照你所说的,第二种没有抛出异常,是因为他不确定装载的是那个类? 但是在Strange2中,也是同样可以解析出来需要的Missing类。而且只要你有Missing m这个声明,虚拟机就一定会将类Missing装载。


进一步怀疑是和try catch的处理机制有关系。如果把异常捕捉的代码都去掉,都是会在mian方法内部抛出异常。

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 11:32:31 得分: 0
 

诚如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类,只有在初始化时才加载.我的理解正确不?谢谢

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 11:36:41 得分: 0
 

分析Strange2的字节码,只有一句不同:
11:astore_2

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

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 11:55:35 得分: 0
 

我也看了下字节码,跟你现在想法大致一样,难道就是因为2个引用占用了同一个局部变量区的地址,所以在载入的时候需要验证公共超类?

Top
TinyJimmy(Jimmy) ( ) 信誉:105 2006-5-15 11:56:03 得分: 40
 

foxty(狐狸糊涂)
不是有Missing m这个声明,虚拟机就一定会将类Missing装载的. 如果Missing是一个接口或者抽象类, Missing不会被装入, 装入的只是Missing的实例对应的类而已. 现在的Strange2的Missing就是类似这样的情况

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 11:59:30 得分: 140
 

应该是这样吧,在Strange1中,变量m和异常ex都属于局部变量,而且2个的作用域是完全不冲突的,所以虚拟机就让这两个局部变量的地址共享了,在载入的时候需要验证公共超类。

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

不知道这个说法是否正确,也是根据你贴出来的文字分析得来得,如果有误还望大家指正。

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 12:03:11 得分: 0
 

TinyJimmy(Jimmy) :

   无论Missng是接口还是抽象类,只要你声明了Missing,在类得常量池中就会有这个引用,在进行常量池解析的时候,就会把这个对应的类、接口或抽象类都会载入,我这里说的载入是载入class文件。类的超类也自然会被先载入。

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 12:28:48 得分: 0
 

to foxty(狐狸糊涂)
确实应该是这样,只因为Strange2中的m并非在try..catch块中声明,ex和m不是共享一个方法的局部变量区,那就不用验证公共超类了.我的理解和你一样.非常感谢您的参与,一开始还真弄不明白.

to TinyJimmy(Jimmy)
你的观点也没错,只是在连接的验证阶段,为了检查字节码的完整性和正确性,我可能提前装载某个类的.

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 12:35:08 得分: 0
 

我的以上说法有误

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

因此TinyJimmy(Jimmy)不够准确,应当Missing不是不被装入,而是并没有被解析
解析是连接阶段的可选步骤,在此步骤才会把在常量池的符号引用解析为直接引用

Top
TinyJimmy(Jimmy) ( ) 信誉:105 2006-5-15 12:41:20 得分: 0
 

怎么解释Strange1的错误没有进入main就抛出, 而Strange2在执行到m = new Missing()才抛出呢?

Top
xingchen0yuxi() ( ) 信誉:100 2006-5-15 12:42:37 得分: 0
 

看看

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 12:43:02 得分: 0
 

对的,你的这个说法我赞同,因为jvm在载入class的时候,只有常量池解析这个动作是延迟的,即使不延迟也是给用户看起来是延迟解析的。所以说Missing类开始是载入了,只是解析动作并未进行。

关于类的装载,在深入java虚拟机一书中有详细的介绍。

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 12:45:26 得分: 0
 

to TinyJimmy(Jimmy) :

   你看看上面的讨论,应该就知道这个puzzle的原因了。这个puzzle其实分析到最后跟载入关系不大,而是跟字节码验证有关西,因为在Strange1中Missng和异常对象占用了同一个局部变量区的地址,所以必须提前解析他们的公共超类,解析的时候就发现Missing类是不存在的,这样就在装载的时候抛出了异常。

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 12:46:17 得分: 0
 

to TinyJimmy(Jimmy)
foxty(狐狸糊涂) 已经解释了,请注意看我上面贴的对于字节码的分析.在这个例子中问题性质不一样.此例的问题是局部变量的作用范围以及解析的时间问题

Top
killme2008(为了生态平衡,请保护蛤蟆) ( ) 信誉:96 2006-5-15 12:47:51 得分: 0
 

感谢大伙
我对Inside the JVM此书的理解又深入了一层

Top
foxty(狐狸糊涂) ( ) 信誉:100 2006-5-15 12:51:19 得分: 0
 

呵呵:) 共同进步~

Top
TinyJimmy(Jimmy) ( ) 信誉:105 2006-5-15 13:49:03 得分: 0
 

明白了, 谢谢foxty(狐狸糊涂), killme2008(zane dennis) 真正的高手

Top
yujie970730() ( ) 信誉:100 2006-5-16 8:54:40 得分: 0
 
Top
whereismyheart() ( ) 信誉:100 2006-5-16 11:56:04 得分: 0
 

看看

Top
guojs_1(博一笑) ( ) 信誉:100 2006-5-16 16:32:19 得分: 0
 

不知道,进来看看!!

Top
TroyorT(大倪) ( ) 信誉:100 2006-5-16 17:55:50 得分: 0
 

有点迷糊!!
呵呵!

Top
qinjiao(秦艽) ( ) 信誉:100 2006-5-16 19:38:28 得分: 0
 

看看


只有注册用户登录后才能发表评论。


网站导航:
 

posts - 2, comments - 2, trackbacks - 0, articles - 4

Copyright © 糊里糊涂