独立的 jdbc-pool 实现内存泄漏

Posted

技术标签:

【中文标题】独立的 jdbc-pool 实现内存泄漏【英文标题】:standalone jdbc-pool implementation memory leak 【发布时间】:2012-02-12 18:34:01 【问题描述】:

我正在尝试在独立的 Web 应用程序中实现 jdbc-pool(自包含 - 不依赖于 server.xml),以便可以将其移动到可能早于 7.0 的 tomcat 安装中。

我正在使用 sourceforge 驱动程序 (net.sourceforge.jtds.jdbc.Driver) 连接到 MSSQL Server

除了这个错误,一切都运行良好:

严重:Web 应用程序 [/jdbc-pool] 似乎已启动 线程名为 [[Pool-Cleaner]:Tomcat Connection Pool[1-12524859]] 但 未能阻止它。这很可能会造成内存泄漏。

基于this 我确定我需要关闭 jdbc-pool 数据源。不过,我在该帖子的最后一行遇到了麻烦:

>> 如果它是在应用程序上下文中配置的,那么这只是 意味着您忘记在连接池上调用 DataSource.close 时 您的网络应用程序已停止。

> 这是令人困惑的建议,因为 javax.sql.DataSource 没有 close() 方法。

为了调用 close,必须将其转换为任何数据 您正在使用的来源。

如何找出我正在使用的数据源类型以及它的类在哪里?我可以以某种方式从驱动程序 jar 中提取它吗?

除了使用池的 servlet 之外,我还使用了 ServletContextListener,以便我可以立即从 contextInitialized 方法开始使用池连接。我开始在这个 ServletContextListener 的 contextDestroyed 方法中添加代码来终止连接,但在问号所在的地方挂断了:

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;

public class JdbcPoolListener implements ServletContextListener 

    @Override
    public void contextInitialized(ServletContextEvent myServletContextEvent) 

        // initialize jdbc-pool datasource to start out with pooled connections 
        try 
            Context myContext = (Context) new InitialContext().lookup("java:comp/env");
            DataSource myDataSource = (DataSource) myContext.lookup("jdbc/db");
            myServletContextEvent.getServletContext().setAttribute("JdbcPool", myDataSource);
         catch (NamingException e) 
            System.out.println("Error initializing jdbc-pool datasource");
            e.printStackTrace();
        
    

    @Override
    public void contextDestroyed(ServletContextEvent myServletContextEvent) 

        // failed attempt to close the data source
        ServletContext myServletContext = myServletContextEvent.getServletContext();
        //DataSource myDataSource = (DataSource) myServletContext.getAttribute("JdbcPool");
        DataSource dataSource = (DataSource)((???) myServletContext.getAttribute(contextAttribute)).getConfiguration().getEnvironment().getDataSource();
        dataSource.close();
        myServletContext.removeAttribute("JdbcPool");

        // deregister JDBC driver to prevent Tomcat 7 from complaining about memory leaks
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) 
            Driver driver = drivers.nextElement();
            try 
                DriverManager.deregisterDriver(driver);
                System.out.println(String.format("Deregistering jdbc driver: %s", driver));
             catch (SQLException e) 
                System.out.println(String.format("Error deregistering driver %s", driver));
                e.printStackTrace();
            
        
    

【问题讨论】:

在您的特定情况下,很可能是 net.sourceforge.jtds.jdbcx.JtdsDataSource 并尝试使用 socketTimeout 设置,如果它空闲,它将关闭连接。更多在驱动程序的文档中。 【参考方案1】:

解决了这个问题,我注意到tomcat.jdbc.pool.DataSourceProxy 有一个关闭方法,所以我将数据源转换为DataSourceProxy,然后调用它关闭。现在日志中不再出现tomcat内存泄漏错误了。

解决方案:

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;

import org.apache.tomcat.jdbc.pool.DataSourceProxy;

public class JdbcPoolListener implements ServletContextListener 

    @Override
    public void contextInitialized(ServletContextEvent myServletContextEvent) 

        // initialize jdbc-pool datasource to start out with pooled connections 
        try 
            Context myContext = (Context) new InitialContext().lookup("java:comp/env");
            DataSource myDataSource = (DataSource) myContext.lookup("jdbc/cf");
            myServletContextEvent.getServletContext().setAttribute("JdbcPool", myDataSource);
         catch (NamingException e) 
            System.out.println("Error initializing jdbc-pool datasource");
            e.printStackTrace();
        
    

    @Override
    public void contextDestroyed(ServletContextEvent myServletContextEvent) 

    // close datasource from proxy?
    ServletContext myServletContext = myServletContextEvent.getServletContext();
        DataSourceProxy myDataSource = (DataSourceProxy) myServletContext.getAttribute("JdbcPool");
        myDataSource.close();       
        myServletContext.removeAttribute("JdbcPool");

        // deregister JDBC driver to prevent Tomcat 7 from complaining about memory leaks
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) 
            Driver driver = drivers.nextElement();
            try 
                DriverManager.deregisterDriver(driver);
                System.out.println(String.format("Deregistering jdbc driver: %s", driver));
             catch (SQLException e) 
                System.out.println(String.format("Error deregistering driver %s", driver));
                e.printStackTrace();
            
        
    

【讨论】:

我猜如果您使用了 org.apache.tomcat.jdbc.pool.DataSource 类,您会得到相同的结果,因为它扩展了 DataSourceProxy 并且 DataSourceProxy 实现了 PoolConfiguration 并且 DataSourceProxy 具有关闭() 方法来关闭池。 @Sergey Benner 感谢您的澄清

以上是关于独立的 jdbc-pool 实现内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

解决Java内存泄漏的一般策略?

为啥基于 TComponent 的接口实现会泄漏内存?

详解ThreadLocal原理及内存泄漏

Javascript内存和泄漏问题

Netty源码-内存泄漏检测toLeakAwareBuffer

如何排查Java内存泄露