Java懒加载异常

3
我知道在Java中,类是通过懒加载的方式进行加载的,这意味着在使用之前不会被加载。但是,异常是否因某种原因而被单独处理?我最近遇到了一个情况,即为异常类获取到一个ClassNotFoundException异常,尽管没有抛出任何异常。
例子:
public class A {    

  public static void main(String[] args) {

      if( args.length == 1 ){
          new C();
      }

      if( args.length > 2 ){

//          try {
//              B.throwAnException();
//          } catch (com.google.protobuf.InvalidProtocolBufferException e) {
//              e.printStackTrace();
//          }
     }
  }

}

B类:

import com.google.protobuf.InvalidProtocolBufferException;

public class B {

  static{
    System.out.println( "Load Class B" );
  }

  static void throwAnException() throws InvalidProtocolBufferException{
    throw new com.google.protobuf.InvalidProtocolBufferException("jkl");
  }

}

类C:

public class C {

  static{
    System.out.println( "Load class C" );
  }

}

当我以一个参数的形式运行这个程序时,我会得到以下结果:
$java A arg1
Load class C

然而,如果我在A类中取消try/catch注释,就会得到以下结果:
$ java A arg1
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/protobuf/InvalidProtocolBufferException

为什么Java在没有抛出异常/未加载类的情况下尝试加载异常类?

1
我认为为了加载一个类,所有的导入也需要可加载。 - NG.
我已经尝试过不使用任何导入并使用完整的类名,但仍然抛出异常。 - rm5248
但是在那个时候,你正在引用一个完整的类 - 为了使JVM能够运行该代码,它至少需要知道你正在引用的类可以被加载。 - NG.
4个回答

2

虚拟机可能需要准备异常跳转表,其中需要包含所有 catch 子句中提到的异常类型。这必须在第一次调用方法之前设置。

如果您的程序是

    if( args.length > 2 )
        throw new InvalidProtocolBufferException();

或者

    if( args.length > 2 )
        try {
            B.throwAnException();
        } catch (Exception e) {
            e.printStackTrace();
        }

由于异常类型未出现在catch子句中,所以一切都会很好。


JLS实际上不强制执行懒惰类加载的方式 - 它可以尽可能地懒惰,另一方面,如果VM选择预先加载所有类,并在无法完成时退出,则JLS也允许。请参见http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.1.2

解析步骤在初始链接时是可选的。实现可以非常早地从正在链接的类或接口解析符号引用,甚至可以递归地从进一步引用的类和接口解析所有符号引用。............... 相反,实现可以选择仅在使用时才解析符号引用 ............. 如果涉及到Test类或任何进一步的递归引用的类和接口,那么在程序执行之前可能会发生加载和链接错误。

然而,在类初始化可以发生的时间上,JLS非常严格。因此,在您的示例中,异常类将被提前加载,但其初始化必须等到达到new InvalidProtocolBufferException()时才能发生。


异常跳转表肯定是问题所在,这就解释了为什么它甚至在到达“main”之前就尝试加载类。 - rm5248

0

JVM将尝试加载由刚刚加载的类直接引用的类。也就是说,它会尝试解析所有可见的内容。

在您上面的示例中,我怀疑您已经针对Google库进行了编译,但在运行时未将其包含在CLASSPATH中。


0

您正在调用引用缺失类的方法。当您运行一个方法(如果不是更早),方法中引用的所有类都必须被解析--换句话说,惰性加载并不像您所想象的那么懒。


0
在JVM中加载类是递归的。当加载一个类时,所有引用的类都将被先加载。 A 引用了 InvalidProtocolBufferException。当加载 A 时,所有被 A 引用的类都会被加载。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接