【问题标题】:How does class loading work when the same class exists in different applications on the same server?当同一个类存在于同一服务器上的不同应用程序中时,类加载如何工作?
【发布时间】:2011-05-07 03:53:07
【问题描述】:

我在一个应用服务器上运行了多个网络应用,每个网络应用 WAR 文件都包含同一个 jar 文件的副本。

这是否意味着该 jar 文件中的类将在 JVM 中加载多次,对于它所在的每个 WAR 文件加载一次?从那以后,如果我在这样的类中有一个静态同步方法,它是否只在它存在的 web 应用程序中的线程之间同步,但不与不同 jar 文件中不同 jar 文件中的同一类中的相同方法同步WAR 文件? (希望问题有意义,如有必要会澄清)。

如果是这种情况,我认为最好的解决方案是从每个 WAR 文件中删除 jar 文件并将其部署到服务器上的共享类路径文件夹中?

【问题讨论】:

    标签: java jakarta-ee classloader war-filedeployment


    【解决方案1】:

    Java 类加载器通常通过以固定顺序在一个或多个位置查找类来工作。例如,当您从命令行运行应用程序时,加载应用程序的类加载器首先会在 rt.jar 文件(以及引导类路径上的其他文件)中查找,然后在您的类路径指定的目录和 JAR 文件中查找。

    webapp 类加载在原理上类似,但在实践中要复杂一些。对于特定的 webapp,webapp 的类加载器按以下顺序查找类。例如 Tomcat 6 按以下顺序查找类:

    1. JVM 的引导类
    2. 系统类加载器类(描述here
    3. /WEB-INF/webapp 的类
    4. /WEB-INF/lib/*.jar webapp
    5. $CATALINA_HOME/lib
    6. $CATALINA_HOME/lib/*.jar

    当然,一旦类加载器找到了它正在寻找的类,它就不会再寻找了。所以顺序后面的同名类不会被加载。

    复杂之处在于,Web 容器为每个 Web 应用程序都有一个类加载器,并且这些类加载器委托给其他管理公共类的类加载器。实际上,这意味着某些类只会为整个容器加载一次(例如 1. 和 2.),而其他类可能会被不同的类加载器加载多次。

    (当一个类被多次加载时,它会导致不同的Class对象和不同的类静态。就JVM而言,类的版本是不同的类型,你不能从一个版本类型转换到其他。)

    最后,可以将 Tomcat 配置为允许“热加载”各个 web 应用程序。这需要停止一个 webapp,为其创建一个新的类加载器,然后重新启动它。

    跟进

    所以...同步静态方法不会保护对已多次加载类的共享资源的访问?

    这取决于细节,但可能不会。 (或者以另一种方式查看,如果一个类实际上被多次加载,那么该类的每个“加载”的 static 方法将访问一组不同的 static 字段.)

    如果您确实希望单个应用程序类实例由同一容器中的多个 webapps 共享,最简单的方法是将类放入 $CATALINA_HOME/lib 或等效项中。但是你也应该问问自己这是否是好的系统设计。考虑组合 webapps,或者使用请求转发等而不是共享数据结构。单例模式在 webapps 中往往很麻烦,这种风格更是如此。

    【讨论】:

    • 感谢您的回复。是的,既然你提到了,我之前就遇到过选角问题。所以回答我的问题,同步静态方法不会保护对多次加载类的共享资源的访问?
    • @Stephane C 在我处理部署在 Tomcat 中的多个 web 应用程序时,我遇到了一个链接异常(资源已加载到不同的类加载器中)。我通过将资源移动到 TC/bin 目录来解决它,以便 TC Cloader 加载它并解决问题。你知道我为什么会收到这个错误吗?为什么不能由 2 个不同 webapps 的 2 个不同类加载器加载相同的资源?
    • 在这个答案中,人们可以读到不同的 WAR 类加载器会导致“相同”类的不同类版本,即。 e.在从 e 加载类 A 时。 G。 WEB-INF/lib 中的 JAR a.jar,它作为完全相同的文件分布在同一服务器(或 EAR)上的不同 WAR 中。当我想在服务器上(或在 EAR 中)的类 A 实例的所有 WAR 上构建缓存时,这将不起作用(除非我将 JAR a.jar 移动到 ~/lib/ext 左右)。 serialVersionUID 此时会拯救我的缓存吗?
    • 请将此作为一个新问题提出,并在其中清楚地解释您所说的“救援”是什么意思,以及您认为serialVersionUID 可能会如何实现这一目标。 (但我认为简短的回答是否定的。)
    【解决方案2】:

    Java EE 应用程序服务器通常使用多个类加载器将应用程序相互隔离,并允许部署一个应用程序的新版本而不影响其他应用程序。

    您会在具有类加载器层次结构的 EAR 中获得多个 WAR 文件和一个 EJB 文件等模式,每个 WAR 都有自己的。

    正如您所描述的,这确实会导致重复,但这不一定是一件坏事。这意味着您甚至可以同时部署相同 JAR 的不同版本,这实际上可能是有益的,允许增量迁移到新版本。

    一些应用程序服务器(例如 WebSphere)明确支持共享库概念,我确实使用了它。

    谨防将 JAR 放入任意类路径中,否则可能会破坏应用服务器本身的稳定性。

    【讨论】:

    • Tomcat 也支持共享库...在 $CATALINA_HOME/lib 中
    • JBoss:它支持“类存储库”。您可以配置 JBoss 以查看来自其他 WAR 的类。但这只会在某些 WAR 未正确取消部署时引起问题,例如内存泄漏和 PermGen 空间错误。
    【解决方案3】:

    大多数应用服务器使用最具体的沿路径优先策略。 如果你有多个库做同样的事情,你应该考虑把它们放在应用程序服务器 lib (f.e: TOMCAT_HOME/lib)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-07
      • 1970-01-01
      • 2014-06-10
      • 1970-01-01
      • 1970-01-01
      • 2023-03-30
      • 1970-01-01
      相关资源
      最近更新 更多