Java Tomcat 7上带有Oracle JDBC驱动程序12c的内存泄漏-Oracle.JDBC.driver线程未能停止

Java Tomcat 7上带有Oracle JDBC驱动程序12c的内存泄漏-Oracle.JDBC.driver线程未能停止,java,tomcat,jdbc,Java,Tomcat,Jdbc,我有一个部署到Tomcat 7.0.54的web应用程序,它使用数据源连接到Oracle 11g数据库。数据源是在META-INF/context.xml中配置的,我将ojdbc7.jar放在/lib中。我使用JNDI查找来检索存储在单例中的数据源,以便每个DAO类都可以使用它 一切正常,但是当我取消部署应用程序(通过Tomcat manager应用程序)时,我在日志中看到: Oct 03, 2014 3:06:55 PM org.apache.catalina.loader.WebappCla

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

一切正常,但是当我取消部署应用程序(通过Tomcat manager应用程序)时,我在日志中看到:

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)初始化期间访问数据源,都会出现问题

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

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


我是否应该降级JDBC驱动程序(如中所建议的)?

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

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

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


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

这个问题不仅仅发生在Oracle JDBC驱动程序(Oracle.JDBC.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser)上

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

同样,Tomcat8.5.X版本也存在这个问题

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

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

其他应用程序部署到我们的生产Tomcat中,有些应用程序的用户群比这个应用程序大得多!当然,我不应该为了重新部署一个应用程序而重新启动生产Tomcat?这不是Tomcat Manager应用程序的用途吗?确保您的oracle驱动程序不在
WEB-INF/lib
目录中。这将自动注册并启动一些OracleJDBC的东西。另请参见.@M.Deinum谢谢,但我已经确保JDBC驱动程序只在
tomcat/lib
中,而不是应用程序的
WEB-INF/lib
中,以防其他人像我一样遇到它;正在使用ojdbc8 v12.2.0.1,据称已修复此问题。正在intellij中运行,由于此消息和随后的崩溃,无法启动服务器。只需使intellij失效并重新启动即可解决此问题/Shugthank感谢“启动时加载”解决方案建议-不幸的是,这没有效果,并且在取消部署时内存泄漏警告仍然存在。嗯,在反编译代码中查找它,发现每个oracle.jdbc.driver.PhysicalConnection都创建了一个“BlockSource”线程。这就解释了为什么“启动时加载”不起作用(我假设它是由驱动程序启动的普通线程,而不是每个连接)。正如您在更新的问题中所描述的,使用较旧的驱动程序版本看起来是一个更好的解决方案。我认为讨论实际上是关于一个类似但不相关的问题,这就是为什么解决方案没有效果。不过,有一个Oracle文档ID和一个可用的补丁:它不是一个“固定大小”的泄漏。它会创建一个很快或稍后会导致OutOfMemory错误的文件。当您查看堆转储时,可以清楚地看到所有停止的web应用程序类加载器都无法被垃圾收集,因为它们的类加载器仍然由
oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser.contextClassLoader引用