繁体   English   中英

ClassNotFoundException 即使我不使用 class

[英]ClassNotFoundException even though I don't use that class

我调用下一个 class 的方法 m1 并且我的类路径中没有 class PKCSException ,据说我不需要它:

package otropack;

import iaik.pkcs.PKCSException;
import iaik.security.provider.IAIK;

public class Test {

    public static void m1(){
        System.out.println("Method 1");
    }
    
    public static void m2() {
        try {
            IAIK.addAsProvider(false);
            System.out.println("Method 2"); 
            throw new PKCSException();
        }catch(PKCSException e) {
            e.printStackTrace();
        }
    }
}

为什么我在调用 Test.m1() 时会得到 ClassNotFountException? 我调用了一个不使用该 class 的方法

Caused by: java.lang.ClassNotFoundException: iaik.pkcs.PKCSException

如果我将 PKCSException 替换为 Exception 它运行良好,而不会因为类路径中没有 class IAIK 而给我一个错误。 我也没有 IAIK 的 class。

非常感谢!

这是一个非常有趣的问题。 答案是验证者。

让我们做实验!

class Test {
    public static void main(String[] args) {
        m1();
    }

    public static void m1() { System.out.println("M1"); }

    public static void neverCalled() throws Exception {
        Exception e = new Ex1();
        // throw e; [1]
        // w1(e); [2]
        // w2(e); [3]
        // w3(e); [3]
    }

    private static void w1(Object x) {}
    private static void w2(Exception x) {}
    private static void w3(Serializable x) {}
}

class Ex1 implements Exception extends Exception {}

然后在命令行上:

> javac Test.java; rm Ex1.class; java Test

行得通

但是,取消注释 [1] 并失败。 取消注释 [2] 并且它有效。 取消注释 [3] 并失败。 取消注释 [4] 并且它有效。

什么。 这。 翻动?

答案是,如果你做了几乎任何有趣的事情,它就会失败。 特别是,“抛出它”或“将其作为参数传递”计数,但不“将其作为参数传递,其中参数类型为jlObject或任何接口”。 这很奇怪。 变量的类型也无关紧要。 Ex1 e = new Ex1(); Exception e = new Ex1(); 甚至Object e = new Ex1(); , 不会有什么不同。

此外,如果我们删除 Ex1.class,而是在其中放置一个打印“正在初始化”的 static 初始化程序,它永远不会打印. 这是 JVM 保证。

那么,到底发生了什么?

是验证者。 使用java -noverify Test运行它,它会在所有情况下成功运行(打印M1 ); 如果需要,请取消注释所有 4 行。

那么发生了什么?

验证程序正在尝试验证javac生成的堆栈帧。 javac 或多或少在生成的字节码中添加了一些注释,以使 class 验证程序的工作更轻松(这是检查执行写入的字节码是否可能导致核心转储或任何安全敏感的重大代码错误的东西)。 验证这些注释是否正确然后分析字节码没有堆损坏故障比推测没有注释就没有堆损坏更容易。

但是,作为检查这些注释的一部分,它需要知道Ex1是什么,并且鉴于 Ex1 不存在,当生成任何堆栈帧注释告诉验证程序堆栈将具有 Ex1 时,您会得到 NoClass 错误里面某处的实例。 因此,问题归结为:VM 何时生成堆栈帧注释,这是一个奇怪的注释,但与上面的片段精确匹配: throw并作为非对象、非接口调用的参数确实如此。 其他情况没有。

您可以从交易中获得实际的验证者错误。 很简单,真的:

> nano Test.java
class Test {
    public static void main(String[] args) {
        Ex1 e = new Ex1();
        m(e);
    }

    public static void m(Exception e) {}
}

> nano E1.java
class E1 extends Exception {}

> javac *.java
> nano E1.java
class E1 extends Thread {} // extend something else
> javac E1.java; # recompile _JUST_ E1
> java Test
VerifierError!

您可以得出的一般结论很简单:

class 中任何地方使用的任何类型都必须在运行时存在,因为否则只能在 [A] 从未调用过的方法中使用类型,并且 [B] 不会出现在任何堆栈帧注释中 - 将避免错误,这两种情况几乎不会同时发生。 但是,class 初始化仅在代码实际运行时发生。 因此,如果您想要编写代码的属性,即使从未实际执行过,也不会由于引用不存在的类而导致任何问题,您可以这样做,但前提是您将这段代码隔离在它自己的 class 中:

package otropack;

import iaik.pkcs.PKCSException;
import iaik.security.provider.IAIK;

public class Test {

    public static void m1(){
        System.out.println("Method 1");
    }
    
    public static void m2() {
        Container.m2();
    }

    private static class Container {
      public static void m2() {
          try {
              IAIK.addAsProvider(false);
              System.out.println("Method 2"); 
              throw new PKCSException();
          }catch(PKCSException e) {
             e.printStackTrace();
          }
      }
    }
}

解决你的问题。 完全没有类加载错误。

当 class 加载器加载您的测试 class 时,它将尝试查找所有依赖类的定义。 在您的代码中,它会因 ClassNotFoundException 而失败,因为您缺少类。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM