Java class is present in classpath but startup fails with Error: Could not find or load main class

Java class is present in classpath but startup fails with Error: Could not find or load main class

我有一个 jar 文件 foobar.jar 包含以下两个 classes:

public class Foo {

    public static void main(String[] args) {
        System.out.println("Foo");
    }
}

另一个 class 看起来像这样:

import javax.batch.api.chunk.ItemProcessor;

public class Bar implements ItemProcessor {

    public static void main(String[] args) {
        System.out.println("Bar");
    }

    @Override
    public Object processItem(Object item) throws Exception {
        return item;
    }
}

如果我使用以下命令执行程序,程序会按预期运行并打印 Foo:

$ java -cp foobar.jar Foo
Foo
$ 

但是如果我尝试使用 class Bar 中的 main 方法启动程序,JVM 会打印启动错误并退出:

$ java -cp foobar.jar Bar
Error: Could not find or load main class Bar
$

这与我尝试使用不在 jar 中的 class 启动程序时的错误相同,例如

$ java -cp foobar.jar BarNotThere
Error: Could not find or load main class BarNotThere
$

为什么会出现此错误? Foo.main 方法可以启动并且我能够从 jar 中反编译 class Bar 的事实证明,class 应该在 class路径。我意识到这可能与接口 ItemProcessor 不在 class 路径上有关。但是在那种情况下我不应该得到一个 java.lang.ClassNotFoundException 吗?

问题确实是接口 ItemProcessor 不在 class 路径上。请注意,错误状态为“findload main class”。在 BarNotThere 的情况下,JVM 确实无法 找到 主要 class。但是在 Bar 的情况下,它无法 load main class.

为了完全加载一个class,JVM还需要每个superclass对象的实例。在 Bar 的这个过程中,JVM 尝试加载 ItemProcessor 的 class 对象。但是由于此接口不在 class 路径上,主 class Bar 的加载失败并且启动以 Error: Could not find or load main class Bar.

终止

如果您难以找到有问题的 class(因为没有消息这么说),您可以使用 jdeps 工具检查 class 路径。只需使用相同的 class 路径,但 运行 jdeps 而不是 java:

$ jdeps -cp foobar.jar Bar
foobar.jar -> java.base
foobar.jar -> not found
   <unnamed> (foobar.jar)
      -> java.io
      -> java.lang
      -> javax.batch.api.chunk                              not found

(这是使用openjdk-9创建的,实际输出可能因Java版本而有很大差异)

这应该会给您足够的提示,告诉您在哪里寻找丢失的 class。


进一步说明

注意加载和初始化 class 之间的区别。如果 classloading 在初始化过程中失败(这意味着 class 已成功 foundloaded),你将得到你的预计 ClassNotFoundException。请参阅以下示例:

import javax.batch.api.chunk.ItemProcessor;

public class FooBar {

    private static ItemProcessor i = new ItemProcessor() {
        @Override
        public Object processItem(Object item) throws Exception {
            return item;
        }
    };

    public static void main(String[] args) {
        System.out.println("Foo");
    }
}

在这种情况下,class FooBar 可以在启动时加载。但它无法初始化,因为静态字段 i 需要 ItemProcessor class,它不在 class 路径上。如果执行 class 上的静态方法,则初始化是先决条件,当 JVM 尝试调用 main 方法时就是这种情况。

$ java -cp foobar.jar FooBar
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.NoClassDefFoundError: javax/batch/api/chunk/ItemProcessor
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
        at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
        at java.lang.Class.getMethod0(Class.java:3018)
        at java.lang.Class.getMethod(Class.java:1784)
        at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: javax.batch.api.chunk.ItemProcessor
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 7 more
$