[spring-projects/spring-boot]记录关闭端点不适合在将 war 部署到 servlet 容器时使用

2019-07-02 988 views
1

当从 Tomcat 取消部署 *.war 文件时(例如通过 tomcat Web 前端),会检测到以下内存泄漏:

02-Jul-2019 11:50:40.143 WARNING [http-nio-8080-exec-5] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-app] appears to have started a thread named [Thread-22] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 java.base@11.0.3/java.lang.Thread.sleep(Native Method)
 org.springframework.boot.actuate.context.ShutdownEndpoint.performShutdown(ShutdownEndpoint.java:67)
 org.springframework.boot.actuate.context.ShutdownEndpoint$$Lambda$3977/0x000000084242f040.run(Unknown Source)
 java.base@11.0.3/java.lang.Thread.run(Thread.java:834)

你能解决这个问题吗?

回答

8

关闭端点只会创建一个线程来关闭应用程序上下文(如果您调用了它)。换句话说,我不认为从 Tomcat 取消部署 war 文件足以重现此问题。此外,关闭端点默认情况下处于禁用状态,并且不适合在将 war 文件部署到容器时使用。相反,您应该使用容器的标准机制来关闭和取消部署应用程序。

2

我将尝试创建一个示例项目。

但与此同时:文档说,/shutdown端点“让应用程序正常关闭”。那么,端点是否仅用于在运行嵌入式 jar 文件而不是 war 容器时关闭应用程序?如果是这样,也许可以将其作为注释添加到文档中? https://docs.spring.io/spring-boot/docs/current/reference/html/development-ready-endpoints.html

那么,在 tomcat 容器中关闭 Spring 战争的正确方法是什么?

5

这是一个公平的观点。我们应该改进这里的文档。

那么,在 tomcat 容器中关闭 Spring 战争的正确方法是什么?

使用容器的标准机制来取消部署应用程序。作为其中的一部分,容器将调用Boot 自动注册contextDestroyed(ServletContext)的方法ContextLoaderListener,这将导致应用程序上下文被关闭。

3

根本原因是management.endpoints.enabled-by-default=true。如果设置(我结合使用它spring-boot-admin-starter来将我的任何执行器端点暴露给 SBA 接口),那么也会ShutdownEndpoint被初始化(尽管默认情况下不暴露给 SBA)。

因此,如果文档enabled by default = No在表中声明shutdown端点,那么我希望应用程序属性...enabled-by-default=true具有相同的效果,并且关闭端点仍应被禁用并且必须显式选择加入。

旁注:由于配置文件不同,我明确设置了此属性。这样,我可以在开发+测试中完全禁用执行器,并通过覆盖提到的属性在生产中启用它:

application.properties [dev]:
management.endpoints.enabled-by-default=false

application-test.properties
(inherits from application.properties)

application-production.properties
management.endpoints.enabled-by-default=true
9

默认情况下,enabled-by-default要么true这里有错误,要么有比您描述的更多的错误。重现该问题的示例战争将会很有用。

5

查找附件示例。这很简单,只需添加actuator依赖项pom.xml并设置属性即可management.endpoints.enabled-by-default=true

将你的断点放入ShutdownEndpoint.setApplicationContext().仅当设置了上述属性时才会调用它。完全删除该属性并且ShutdownEndpoint永远不会调用(尽管正如您所说,该属性的默认值也是=true)。

关机示例.zip

2

另外,如果您使用 编译我的示例项目<packaging>war</packaging>,将其部署到tomcat网络服务器,访问已部署的应用程序,然后只需取消部署该应用程序即可。那么在catalina.outlogs中确实可以找到内存泄漏的日志。

因此,如果ShutdownEndpoint在 tomcat 容器内启动,肯定会造成内存泄漏。也许我们这里有多个不同的问题?

7

默认enabled-by-default值为true

这是我记错了,抱歉。默认值enabled-by-default是没有值,这意味着使用每个单独端点的默认启用。这false适用于关闭端点,也适用于记录的所有其他端点。当您设置时,management.endpoints.enabled-by-default=true它会配置每个端点的默认启用。然后可以使用各个属性对启用进行微调management.endpoint.<id>.enabled

8

我这里也错了。再次抱歉。样本让我看到了正在发生的事情。当关闭端点启用时,它会作为一个 bean 公开。框架支持推断 bean 的 destroy 方法并将其包装在 a 中,DisposableBeanAdapter查找该shutdown方法并假定它是一个在关闭上下文时应该调用的 destroy 方法。当 Tomcat 取消部署应用程序时,上下文将关闭。这会导致端点的shutdown方法被调用,因为它被视为销毁方法。结果,创建了一个线程来通过关闭上下文来关闭应用程序。该线程会阻塞已在进行的关闭处理的剩余部分,这会导致 Tomcat 将其检测为泄漏线程。一旦第一次关闭尝试完成,第二次关闭尝试将无操作,线程将退出。

6

因此,就我而言,我必须显式设置management.endpoint.shutdown.enabled=false以禁用关闭端点。

6

我正在使用war资源在Tomcat上部署Spring Boot应用程序,当我在Tomcat上本地部署它时,同时收到内存泄漏错误消息。顺便说一句,我通过参考这里的文档在我的应用程序中实现了正常关闭。

应用程序版本:

  • Tomcat - 9.0.64
  • Spring Boot - 2.7.1
  • Packaging - War
  • Java - 1.8
  • Project - Gradle
  • Gradle - 7.4.1

我找到了另一种手动实现它的方法,即获取 Tomcat 的连接器对象并等待宽限期,以允许所有飞行请求在 @PreDestroy 注释的帮助下完成,然后再进入关闭阶段。然而,我只是想知道是否有更好的方法来实现这一点,因为这是许多开发人员能够实现的一个小问题。

3

@freeman9397 您链接到的部分中记录的优雅关闭适用于嵌入式 Web 服务器。如果您将 war 文件部署到 Tomcat,则关闭不受 Spring Boot 的控制。

2

@philwebb 感谢您的快速更新。除了我提到的以外,您是否知道通过 war 文件部署应用程序时如何处理正常关闭的方法?