【问题标题】:Why would Java classloading fail on Linux, but succeed on Windows?为什么 Java 类加载在 Linux 上会失败,但在 Windows 上会成功?
【发布时间】:2009-02-06 10:36:24
【问题描述】:

我有一个使用 Jetty 部署的 Java Web 应用程序(使用 Spring)。如果我尝试在 Windows 机器上运行它,一切都会按预期运行,但如果我尝试在我的 Linux 机器上运行相同的代码,它会失败,如下所示:

[正常启动输出]
11:16:39.657 INFO [main] org.mortbay.jetty.servlet.ServletHandler$Context.log>(ServletHandler.java:1145) >16> 设置 Web 应用根系统属性:'webapp.root' = [/path/到/工作/目录]
java.lang.reflect.InvocationTargetException
        在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        在 java.lang.reflect.Method.invoke(Method.java:597)
        在 org.mortbay.start.Main.invokeMain(Main.java:151)
        在 org.mortbay.start.Main.start(Main.java:476)
        在 org.mortbay.start.Main.main(Main.java:94)
引起:java.lang.ExceptionInInitializerError
        在 org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:129)
        在 org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:51)
        在 org.mortbay.jetty.servlet.WebApplicationContext.doStart(WebApplicationContext.java:495)
        在 org.mortbay.util.Container.start(Container.java:72)
        在 org.mortbay.http.HttpServer.doStart(HttpServer.java:708)
        在 org.mortbay.util.Container.start(Container.java:72)
        在 org.mortbay.jetty.Server.main(Server.java:460)
        ... 7 更多
引起:org.apache.commons.logging.LogConfigurationException:org.apache.commons.logging.LogConfigurationException:没有合适的日志构造函数[Ljava.lang.Class;@15311bd for org.apache.commons.logging.impl.Log4JLogger(引起by java.lang.NoClassDefFoundError: org/apache/log4j/Category) (由 org.apache.commons.logging.LogConfigurationException 引起:没有合适的日志构造函数 [Ljava.lang.Class;@15311bd 用于 org.apache.commons.logging。 impl.Log4JLogger(由 java.lang.NoClassDefFoundError: org/apache/log4j/Category 引起))
        在 org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:543)
        在 org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:235)
        在 org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:209)
        在 org.apache.commons.logging.LogFactory.getLog(LogFactory.java:351)
        在 org.springframework.util.SystemPropertyUtils.(SystemPropertyUtils.java:42)
        ... 14 更多
引起:org.apache.commons.logging.LogConfigurationException:没有合适的日志构造函数 [Ljava.lang.Class;@15311bd for org.apache.commons.logging.impl.Log4JLogger(由 java.lang.NoClassDefFoundError 引起:org/apache /log4j/类别)
        在 org.apache.commons.logging.impl.LogFactoryImpl.getLogConstructor(LogFactoryImpl.java:413)
        在 org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:529)
        ... 18 更多
引起:java.lang.NoClassDefFoundError: org/apache/log4j/Category
        在 java.lang.Class.getDeclaredConstructors0(本机方法)
        在 java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
        在 java.lang.Class.getConstructor0(Class.java:2699)
        在 java.lang.Class.getConstructor(Class.java:1657)
        在 org.apache.commons.logging.impl.LogFactoryImpl.getLogConstructor(LogFactoryImpl.java:410)
        ... 19 更多
引起:java.lang.ClassNotFoundException:org.apache.log4j.Category
        在 java.net.URLClassLoader$1.run(URLClassLoader.java:200)
        在 java.security.AccessController.doPrivileged(本机方法)
        在 java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        在 java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        在 java.lang.ClassLoader.loadClass(ClassLoader.java:252)
        在 java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
        ... 24 更多
[关机输出]

我已经使用java -verbose:class 运行该应用程序,根据该输出,org.apache.log4j.Category 是从我的 /WEB-INF/lib 中的 log4j JAR 加载的,就在引发第一个异常之前。

现在,两台机器上的 Java 版本略有不同。两台机器都有 Sun 的 java,Linux 机器有 1.6.0_10,而 Windows 机器有 1.6.0_08,或者可能是 07 或 06,我现在不记得确切的数字,并且手头没有机器.但即使 Java 的次要版本略有不同,代码也不应该像这样中断。有谁明白这里出了什么问题?

【问题讨论】:

    标签: java cross-platform classloader


    【解决方案1】:

    您必须明白,类加载器无法看到所有内容;他们只能看到父类加载器已加载的内容或他们自己加载的内容。因此,如果您有两个类加载器,一个用于 Jetty,另一个用于您的 webapp,您的 webapp 可以看到 log4j(因为 JAR 是 WEB-INF/lib)但 Jetty 的类加载器不能。

    如果您设法使一个使用 log4j 但最终在 Jetty 的上下文(和类加载器)中运行的 Jetty 可用的类(例如 DB 层中的某些东西),您将收到错误。

    要对此进行调试,请在 org.springframework.web.util.Log4jWebConfigurer.initLogging() 中设置断点。如果可以的话,将这个类的源代码复制到你的项目中(之后不要忘记删除它)并添加这一行:

    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    

    查看调试器中的 cl 对象。这应该会给你一些创建它的信息。我猜这是 Jetty 的类加载器。

    [EDIT] 请注意,如果您在两个类加载器中都有 log4j,您会遇到不同的混乱:在这种情况下,您将有 两个 具有 same 名称的类创建与分配不兼容的对象!所以要确保这个 jar 只有一个实例,否则 log4j 的实例永远不会在两个上下文之间传递(这通常是不可能的)。

    【讨论】:

    • 非常感谢!基本上就是这样。 Jetty 的版本也存在差异,Linux 上的 5.1.11 与 5.1.14(我真的应该考虑检查),并且 .14 默认情况下不像 .11 那样加载 log4j。将 log4j JAR 暴露给 Jetty 类加载器修复了它。
    • 好点。将来我一定会牢记这一点。但正如您所指出的,Log4J 实例不太可能在 Jetty 和应用程序之间传递,因此在这种情况下应该不是问题。再次感谢您的建议。
    【解决方案2】:

    这似乎是一个经典的类加载器问题。这可能是由于首先加载了另一个 Web 应用程序,该应用程序也使用 log4j,但版本与您正在测试的应用程序使用的版本不同。类加载器使用它找到的类的第一个版本。服务器类加载策略通常可以在配置文件中更改。抱歉,我对此有点生疏,但也许它可以为您指明正确的方向。

    1. 确保网络服务器上没有安装其他应用,
    2. 确保正在加载的 log4j 是正确的版本,
    3. 确保您没有潜伏在服务器类路径中某处的 log4j。

    HTH

    【讨论】:

    • -1:应用服务器为每个应用使用专用的类加载器。不同的网络应用不会相互干扰。
    • 不正确——取决于服务器类加载器的配置方式。
    【解决方案3】:

    您在两台机器上使用相同的 WAR?您是否检查过 WAR 文件是否相同(未发生传输错误)?

    【讨论】:

      【解决方案4】:

      需要考虑的一些随机事项:

      (1) 检查是否有任何其他版本的 log4j 漂浮在 linux 实例上,在 web-app 目录之外?

      (2) 是否使用了 apache commons 日志记录?您可能想考虑使用SLF4J

      (3) JAR/WAR 是否以某种方式损坏 - 它是以 ASCII 格式还是二进制格式通过 FTP 传输的?

      (4) 打印出每种情况下的类加载器层次结构,看看是否有差异?

      【讨论】:

        【解决方案5】:

        尽管最初的问题已经为提问者解决了,但我要指出,在 Windows 和 Linux(或 Unix)上运行相同代码时,常见的问题来源是区分大小写的问题。 Windows 忽略大小写,而 Linux 或 Unix 区分大小写。这已经不止一次地咬我了。

        因此,如果您在类路径上指定了一个 jar 或目录,但情况不正确,那么它将在 Linux 上失败,但在 Windows 上成功。这也可能是 FileNotFoundExceptions 的来源。

        【讨论】:

          【解决方案6】:

          遇到了同样的问题并找到了一个简单的解决方案/解决方法:

          在 Eclipse 中的 Preferences > Java > Installed JREs 中,选择 JRE > Edit and Add External JARs... 并浏览到您的 log4j.jar。

          另一种解决方法是将 log4j.jar 添加到 Classpath 选项卡中的每个启动定义。

          【讨论】:

            猜你喜欢
            • 2010-12-13
            • 1970-01-01
            • 2020-11-07
            • 2012-02-10
            • 2018-04-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多