【问题标题】:Memory leak on Tomcat 7 with Oracle JDBC drivers 12c - oracle.jdbc.driver thread failed to stop带有 Oracle JDBC 驱动程序 12c 的 Tomcat 7 上的内存泄漏 - oracle.jdbc.driver 线程无法停止
【发布时间】:2014-11-28 14:52:15
【问题描述】:

我有一个部署到 Tomcat 7.0.54 的 Web 应用程序,它使用数据源连接到 Oracle 11g 数据库。数据源在META-INF/context.xml 中配置,我将ojdbc7.jar 放在<tomcat-install-dir>/lib 中。我使用 JNDI 查找来检索存储在单例中的数据源,以便每个 DAO 类都可以使用它。

一切都按预期工作,但是当我取消部署应用程序(通过 Tomcat 管理器应用程序)时,我在日志中看到:

Oct 03, 2014 3:06:55 PM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/myapp] appears to have started a thread named [oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser] but has failed to stop it. This is very likely to create a memory leak.
Oct 03, 2014 3:06:57 PM org.apache.catalina.startup.HostConfig undeploy
INFO: Undeploying context [/myapp]

当我调试时,我可以看到一旦访问数据库(通过数据源)就创建了这个线程。

我的数据源配置:

<Context antiResourceLocking="false">
    <Resource name="jdbc/myapp" auth="Container" 
        type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver" 
        maxActive="20" maxIdle="10" maxWait="-1"
        username="myuser" password="mypass"
        url="jdbc:oracle:thin:@myserver:1521:mysid"
        removeAbandoned="true" removeAbandonedTimeout="10" logAbandoned="true"
        validationQuery="SELECT 1 FROM DUAL"   
        testOnBorrow="true" testOnReturn="true" testWhileIdle="true"  
        timeBetweenEvictionRunsMillis="1800000" numTestsPerEvictionRun="3"  
        minEvictableIdleTimeMillis="1800000"
    />
</Context>

编辑

进一步调查显示,无论在应用程序(或 servlet)初始化期间是否访问数据源,都会出现此问题。

实际上,问题线程只是被创建,因此问题只存在于使用 12c 版本的 Oracle JDBC 驱动程序(ojdbc6.jar 或 ojdbc7.jar)时。

如果我恢复使用 ojdbc6.jar 的 11.2.0.4 版本,则永远不会创建线程并且永远不会出现内存泄漏警告。

我是否应该降级 JDBC 驱动程序(如 https://stackoverflow.com/a/9177263/4105953 中的建议)?

【问题讨论】:

  • 确保您的 oracle 驱动程序不在WEB-INF/lib 目录中。因为这会自动注册并启动一些 Oracle JDBC 的东西。另请参阅here
  • @M.Deinum 谢谢,但我已经确保 JDBC 驱动程序仅在 tomcat/lib 而不是应用程序的 WEB-INF/lib
  • 以防其他人像我一样遇到这种情况;正在使用据称已修复此问题的 ojdbc8 v12.2.0.1。在 intellij 中运行它并且由于此消息和随后的崩溃而无法启动服务器。通过简单地使intellij无效并重新启动来解决。 /耸肩

标签: java tomcat jdbc


【解决方案1】:

此问题不仅仅发生在 Oracle JDBC 驱动程序 (oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser) 中。

如果任何第 3 方依赖项启动一个守护线程来执行某些任务,那么在停止 Tomcat 时也会为它们记录警告消息。

此外,Tomcat 8.5.X 版本也存在此问题。

解决方案: 我发现的解决方案是获取当前线程的线程组并中断它。它将确保在关闭 Tomcat 之前,它的所有守护线程都被杀死。

下面的代码应该添加到“contextDestroyed”方法中 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); threadGroup.interrupt();

【讨论】:

    【解决方案2】:

    我发现一个关于主题here 的冗长讨论。结论是它会造成“固定大小的内存泄漏”,即后续的重新部署不会增加内存泄漏。
    我没有 Oracle 支持访问权限,但讨论中提到的错误 ID 是 16841748(2013 年 5 月,现在可能已解决)。

    一种可能的解决方法是在通过tomcat/conf/web.xml 中配置为“启动时加载”的自定义 servlet 启动 Tomcat 时实际使用一次数据源(获取连接、执行虚拟查询、关闭连接)。这应该会在您的 Web 应用程序的类加载器范围之外启动 Oracle 驱动程序线程(另请参阅关于驱动程序线程的 FAQ),从而防止“固定大小的内存泄漏”。

    请注意,MySQL JDBC 驱动程序也存在类似问题,但有一个decent solution。对于 Oracle JDBC 驱动程序的最新版本(我不知道),可能存在这样的解决方案。

    【讨论】:

    • 感谢“启动时加载”解决方法建议 - 不幸的是,这没有效果,并且内存泄漏警告在取消部署时仍然存在。
    • 嗯,在反编译代码中查了一下,发现每个oracle.jdbc.driver.PhysicalConnection都创建了一个“BlockSource”线程。这就解释了为什么“启动时加载”不起作用(我假设它是由驱动程序启动的一般线程,而不是每个连接)。如您在更新的问题中所述,使用较旧的驱动程序版本似乎是一个更好的解决方案。
    • 我认为讨论实际上是关于一个类似但不相关的问题,这就是解决方法无效的原因。不过,有一个 Oracle 文档 ID,还有一个可用的补丁:JDBC Thin Driver Leaks Threads With Tomcat (Doc ID 1901449.1)
    • 这不是“固定大小”的泄漏。它会创建classloader leaks,它迟早会出现 OutOfMemory 错误。当您查看堆转储时,您可以清楚地看到所有停止的 Web 应用程序类加载器都无法被垃圾收集,因为它们的类加载器仍然被 oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser.contextClassLoader 引用
    【解决方案3】:

    这是正常的,当你在Tomcat中热部署时会发生这种情况。它不会在生产中正常给您带来任何问题,因为您通常不会在生产中保持热部署更新,您只需停止并重新启动服务器即可。

    【讨论】:

    • 其他应用程序部署到我们的生产 Tomcat 中,其中一些应用程序的用户群比这个应用程序大得多!当然,我不应该仅仅为了重新部署应用程序而重新启动生产 Tomcat 吗?这不就是 Tomcat Manager 应用的用途吗?
    猜你喜欢
    • 2012-11-25
    • 2012-03-29
    • 1970-01-01
    • 2018-02-09
    • 2012-02-03
    • 2014-06-01
    • 2016-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多