我们有一个在 Tomcat 上运行的 Spring Boot 应用程序,它是一个 RESTful Web 服务。在我们的测试环境和生产环境中的 3 个 Tomcat 实例上部署了相同的 WAR 文件。在运行性能测试时,我们注意到某些服务器存在一个特殊问题。一些服务器在处理大约 2500 个请求后停止响应。该问题发生在 3 台生产服务器中的 2 台和 3 台测试服务器中的 1 台上。
在出现问题的服务器上,我们在 JVM 监控中注意到,每当我们运行性能测试时,加载的类计数都会不断增加。加载的类数从 20k 增加到大约 200 万。当类计数接近 200 万时,JVM 监控还显示 GC 花费的时间太长,超过 40 秒。一旦达到该点,应用程序将停止响应。应用程序抛出 OutOfMemoryException“压缩类空间”。如果我们继续发送更多请求,我们可以从应用程序日志中看到该服务仍在接收请求,但中途停止处理。
在没有问题的其他服务器上,类加载计数保持在 20k 不变。而且 GC 也很正常,用时不到 1 秒。
我们注意到的其他测试和行为 -
- 此问题发生在安装在 Windows PC 上的本地 Tomcat 实例上。服务器在 Linux 上。 OpenJDK 和 Oracle JDK 1.8 都会出现此问题。
- 我们验证了 Tomcat 实例彼此相同 - 我们甚至从工作服务器克隆并将它们放在坏服务器上。
- 使用不同的 GC 策略(PS、CMS 和 G1)进行了测试,所有这三个策略都出现了问题。
- 通过将应用程序作为独立的 Spring Boot JAR 运行进行测试,问题消失了。类计数保持不变,GC 行为正常。
- 该应用程序目前正在使用 JAXB 库执行 XML 编码/解码,我们在代码中找到了可以优化代码的地方。重构代码并迁移到 Jackson 库是另一种选择。
我的问题是 -
- 当我们部署同一个 WAR 文件时,是什么导致了多个服务器之间的差异?
- 是什么导致了作为部署在 Tomcat 上的 WAR 运行的应用程序与作为独立的 Spring boot 应用程序运行的应用程序之间的差异?
- 如果我们对 JVM 进行堆转储或进行分析,需要注意哪些事项?
最佳答案
所以事实证明这是由于我们的类路径中的 jaxb 2.1 jar。感谢 Mark 指出 jaxb 的已知错误。
我们的应用程序没有明确地将 jaxb-impl 作为依赖项,因此一开始很难看出。查看 Maven 依赖关系树后,我们发现正在从其他项目和库加载两个不同的版本。我们的应用程序在类路径中有 jaxb-impl 版本 2.1 和 2.2.6。我们将 2.1 版本作为应用程序的 pom.xml 中的排除项并解决了该问题。
我的猜测是不同的服务器在应用程序启动时加载了不同的版本。这可能就是为什么有些服务器工作正常而其他加载 2.1 版本的服务器出现问题的原因。与作为独立的 Spring boot 应用程序运行类似,它可能加载了 2.1 版本。
关于java - 在 Tomcat 上部署为 WAR 的 Spring Boot 应用程序中增加类加载计数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58055998/