最近频繁遇到连接池满的问题,已经在dbcp中设置连接有效性校验
testonreturn,testOnBorrow,testWhileIdle都设置为true了
而且也设置了removeAbandoned来回收空闲连接。
也检查了程序,应该不是忘记close的缘故但总是连接池满。请问各位高手是否有方法能够打印出当前连接池中的对象都在执行什么sql语句。
是否可以显式的将连接池中的某一个datasource注销,并重新构建这个datasource,
而不用重启tomcat服务。

解决方案 »

  1.   

    可能的原因:
    1.还是有部分地方连接没放回连接池。或者说,close连接不是在finally里面,这样假如有异常连接可能就不会被回收。
    2.你的某个sql执行时间实在太久。
      

  2.   

    感谢高人这么快就给回复了
    我这里确实检查过所有close都是放在finally里面了,而且数据库操作封装了专门的中间件,还编写了killer 通过专用数据库连接在执行alert system kill session 的方式杀掉超时的会话。
    仍然无法解决连接池满的问题。
    不过这种问题确实发生在数据库存在部分异常,一下请求非常耗时的情况。因此也希望从销毁连接池重新在web容器中建立连接池、以及打印连接池所有连接的sqlstatement 来分析
    优化的方法。所以请对dbcp原理熟悉的高手能够解答一下,如何调试tomcat 的dbcp,如何在不重启tomcat的情况下重新创建连接池。
      

  3.   

    大概原因:
    1.还是有部分地方连接没放回连接池。或者说,close连接不是在finally里面,这样假如有异常连接可能就不会被回收。
    2.你的某个sql执行时间实在太久。ww
      

  4.   

    感谢各位的回复 本人更多的问题是如何在数据库存在异常的情况下,能够重启连接池而不用重启tomcat,这样可以不影响用户使用。
    另外如何打印出当前连接池的对象明细,这样方便数据库端进行优化。
      

  5.   


    tomcat连接池,只管理那些不用动态重启的连接。对于你的需求,可以自己写一套连接管理,将可能需要动态管理的连接信息配置进去,
    把你的“重启”操作写到你的连接管理里;想什么时候重启调用一下就行了。
      

  6.   

    查到一些资料与大家分享,希望能够深入讨论
    connection耗尽不一定就是由connection leak引起,如果你的执行队列中线程数设置的比connection pool大,而且你的某些程序占用connection时间过长,致使执行队列中的线程已经把connection pool中的所有的connection都申请出来了,此时如果再有新的执行线程响应请求申请connection,pool中已经告罄,connection就无从得到了,只有等待。这从控制台的connection监控中是看得出的。这种情况下,如果程序能正确关闭connection,connection还是会被释放的,不会泄露。还有一种情况是已获得connection的线程间发生了死锁,没法去释放connection,使得connection被一直占用。这要去重点检查一下应用中的synchronize代码段和涉及数据库锁的代码。同时可以定时,尤其是系统挂了后作一下thread dump,重点看看运行中和等待资源的线程在忙什么和等什么。如果要查connection leak,还是得从应用入手,仔细检查connection的分配与释放。确保connection的释放代码放在finally段中。还有就是重点检查connection有没有被一些static对象引用。泄露往往就发生在这里。JVM的GC对付一般的Connection leak还是很有效的,比如说未关闭的connection只是函数中的局部变量且并未被其它对象引用。但GC对被Static对象引用的connection是有心无力。用profiling工具可以辅助侦测connection leak,具体做法是在CPU的代码执行视图的代码执行树里,检查get connection与close connection的执行次数是否相同?如果get connection执行次数大于close connection,这就要重点检查一下了。这一类的工具有很多,常见的有:JProfiler,JProbe,OptimizeIt
    ------------------------------------------------------------------------
    DBCP代码研读以及就数据库连接失效的解决
    问题网上很多评论说DBCP有很多BUG,但是都没有指明是什么BUG,只有一部分人说数据库如果因为某种原因断掉后再DBCP取道的连接都是失效的连接,而没有重新取。就此研读了一下DBCP的代码,共享之。分析DBCP使用apache的对象池ObjectPool作为连接池的实现,有以下主要的方法Object borrowObject() throws Exception;从对象池取得一个有效对象void returnObject(Object obj) throws Exception;使用完的对象放回对象池void invalidateObject(Object obj) throws Exception;使对象失效void addObject() throws Exception;生成一个新对象
    ObjectPool的一个实现就是GenericObjectPool,这个类使用对象工厂PoolableObjectFactory实现对象的生成,失效检查等等功能,以其实现数据库连接工厂PoolableConnectionFactory做以说明,主要方法:     Object makeObject() throws Exception; 使用ConnectionFactory生成新连接     void destroyObject(Object obj) throws Exception;关闭连接     boolean validateObject(Object obj); 验证连接是否有效,如果_validationQuery不空,则使用该属性作为验证连接是否有效的sql语句,查询数据库     void activateObject(Object obj) throws Exception;激活连接对象     void passivateObject(Object obj) throws Exception; 关闭连接生成过的Statement和ResultSet,使连接处于非活动状态而GenericObjectPool有几个主要属性     _timeBetweenEvictionRunsMillis:失效检查线程运行时间间隔,默认-1     _maxIdle:对象池中对象最大个数     _minIdle:对象池中对象最小个数     _maxActive:可以从对象池中取出的对象最大个数,为0则表示没有限制,默认为8在构造GenericObjectPool时,会生成一个内嵌类Evictor,实现自Runnable接口。如果_timeBetweenEvictionRunsMillis大于0,每过_timeBetweenEvictionRunsMillis毫秒Evictor会调用evict()方法,检查对象的闲置时间是否大于 _minEvictableIdleTimeMillis毫秒(_minEvictableIdleTimeMillis小于等于0时则忽略,默认为30分钟),是则销毁此对象,否则就激活并校验对象,然后调用ensureMinIdle方法检查确保池中对象个数不小于_minIdle。在调用returnObject方法把对象放回对象池,首先检查该对象是否有效,然后调用PoolableObjectFactory 的passivateObject方法使对象处于非活动状态。再检查对象池中对象个数是否小于_maxIdle,是则可以把此对象放回对象池,否则销毁此对象还有几个很重要的属性,_testOnBorrow、_testOnReturn、_testWhileIdle,这些属性的意义是取得、返回对象和空闲时是否进行验证,检查对象是否有效,默认都为false即不验证。所以当使用DBCP时,数据库连接因为某种原因断掉后,再从连接池中取得连接又不进行验证,这时取得的连接实际已经时无效的数据库连接了。网上很多说DBCP的bug应该都是如此吧,只有把这些属性设为true,再提供_validationQuery语句就可以保证数据库连接始终有效了,oracle数据库可以使用SELECT COUNT(*) FROM DUAL,不过DBCP要求_validationQuery语句查询的记录集必须不为空,可能这也可以算一个小小的BUG,其实只要_validationQuery语句执行通过就可以了。 注意事项所以使用DBCP连接池放必须注意构造GenericObjectPool对象时     validationQuery:SELECT COUNT(*) FROM DUAL       _testOnBorrow、_testOnReturn、_testWhileIdle:最好都设为true       _minEvictableIdleTimeMillis:大于0 ,进行连接空闲时间判断,或为0,对空闲的连接不进行验证     _timeBetweenEvictionRunsMillis:失效检查线程运行时间间隔,如果小于等于0,不会启动检查线程
    ------------------------------------------------------------------------
    1) maxActive 连接池的最大数据库连接数。设为0表示无限制。
    2) maxIdle  数据库连接的最大空闲时间。超过此空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制。
    3) maxWait 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
    4) removeAbandoned 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中。
    5) removeAbandonedTimeout 数据库连接过多长时间不用将被视为被遗弃而收回连接池中。
    6) logAbandoned 将被遗弃的数据库连接的回收记入日志。
    ------------------------------------------------------------------------
      

  7.   

    dbcp尚且有这么多的不完善,何况自己写连接池管理呢。所以希望有研究过dbcp源码的,帮帮忙,说说dbcp有没有提供外部显式控制连接池创建于销毁的,以及连接池对象跟踪的接口。
      

  8.   

    请问楼主是web应用吗?java的开源组件这么多, 非要用dbcp吗,有很多的连接池组件可用,如果dbcp不好用,就换个了
      

  9.   

    多数情况下是代码的问题,就算一个连接花费时间过长也可能是 SQL 写法不好或没索引,或允许用户不输入任何条件查询所有记录(如果有一百成条记录呢?)。1.人工检查代码.
    2.用 AOP 把获取连接的地方做 advice,给它加上 after return advice 到 getConnection 和 Connection.close() 方法,getConnection 的 advice 将当前线程的某个资源 checkOut,close 方法的 advice 则将它 checkIn,并在后台开启一个线程,它记录着每个线程 checkOut 的资源并放入当前线程的一个队列中,在checkIn 之后从当前线程对应的队列中清除。用这种办法将申请资源和归还资源实时登记,后台线程定时检查所有线程的队列,如果一个线程已经终止,它的资源队列应该是空的。这种方法是动态检测,不过约定是每个线程独立管理资源,不可以把资源当参数传递到另一个线程,在每一个申请资源 checkOut 时把当前线程的堆栈保存下来,方法就是保存一个 new Exception() 对象,当我们后来发现有资源未释放时把这个 Exception 打印出来就能看到当时是调用哪个方法得到的那个连接未释放,便于检查代码。这个方案中用线程终止来作为资源征用生命周期的边界,对于有线程池的环境来说不成立,但我们能找到新的确定边界的方法,比如 Web 程序用 Servlet Filter 来处理,EJB 则在 主控业务方法上加个 advice 处告诉 Monitor 这个生命周期已结束,并开始这个线程的资源使用情况。下面这个 DebugAspect 是一个使用 AspectJ 的部分源码(AspectJ 仅仅是扩展 Java 语法提供一些新的关键字,这个源最终会被编译成一个 class 文件),它可以用于在开发环境中测试时检查资源的使用情况。如果你熟悉 AOP 的话, Spring AOP 也可以同样做到。使用 AspectJ 的编译器,它个 DebugAspect 将会被编译成 .class,并且它会将所有 pointcut 匹配到的方法调用所在的类直接修改这个类的.class字节码,把它的 advice 代码嵌入到合适位置。举例:
    class A {
       private void cleanup(Connection conn) throws Exception {
           if (conn != null ) { conn.close(); }
       }
    }在被 AspectJ 做过 Weaving 之后就等价于这样的代码:
    class A {
       private void cleanup(Connection conn) throws Exception {
           if (conn != null ) { 
               conn.close(); 
               DebugAspect aj = ... // 得到当前对象对应的 DebugAspect 的实例。
               //调用对应的 DebugApsect 里面的那个 after()returning(Connection conn) 方法。
               //这个 xxx$after ... 方法名是编译时随机生成的。   
               aj.xxx$after_returning_closeConn(conn); 
           }
       }
    }
    public aspect DebugAspect {  protected pointcut openConn():(call(public Connection DataSource.getConnection()) || call(* DriverManager.getConnection(..)));  protected pointcut closeConn():call(public void Connection.close());
       
      protected pointcut openStatement():(call public Statement+ Connection.*(..));
      
      protected pointcut closeStatement():(call public void Statement+.close());
      
      protected pointcut openResultSet():(call public ResultSet Statement+.execute*(..));
      
      protected pointcut closeResultSet():(call public void ResultSet.close());    after()returning(Connection conn):openConn(){
            //log(getCaller()+" --> Already connect to datasource.");
            checkout(conn);
        }
        
        after()returning():closeConn(){
            //log(getCaller()+" --> Already release jdbc connection.");
            checkin(thisJoinPoint.getTarget());
        }
        
        after()returning(Statement stmt):openStatement(){
            checkout(stmt);
        }
        
        after()returning():closeStatement(){
            checkin(thisJoinPoint.getTarget());
        }
        
        after()returning(ResultSet rs):openResultSet(){
            checkout(rs);
        }
        
        after()returning():closeResultSet(){
            checkin(thisJoinPoint.getTarget());
        }    private final Map items = Collections.synchronizedMap(new HashMap());    protected synchronized void checkout(Object resource, String caller) {
          if (resource == null) {
            return;
          }      checkMonitor(); // 后台线程每隔 15 秒检查一次 repository 中哪个线程已经终止了但它的资源队列不是空的。      Set resources = (Set) items.get(Thread.currentThread());      if (resources == null) {
            resources = Collections.synchronizedSet(new HashSet());        items.put(Thread.currentThread(), resources);
          }      resources.add(new ResourcePair(resource));      log("[check out(" + Thread.currentThread() + "," + resource + ")" + (caller == null ? ", caller :" + caller : "")
              + "]", false, Repository.class);
        }    protected synchronized void checkin(Object resource, String caller) {
          if (resource == null) {
            return;
          }      checkMonitor();      Set resources = (Set) items.get(Thread.currentThread());      if (resources != null) {
            ResourcePair pair = new ResourcePair(resource);        if (resources.remove(pair)) {
              log("[check  in(" + Thread.currentThread() + "," + resource + ")"
                  + (caller == null ? ", caller :" + caller : "") + "]", false, Repository.class);
            } else {
              log("what's this? :" + Thread.currentThread() + "," + resource + ")"
                  + (caller == null ? ", caller :" + caller : "") + "]", false, Repository.class);
            }
          } else {
            log("what's this? :" + Thread.currentThread() + "," + resource + ")"
                + (caller == null ? ", caller :" + caller : "") + "]", false, Repository.class);
          }
        }        private synchronized void checkMonitor() {
          if (monitor != null && monitor.isAlive()) {
            return;
          }      monitor = new Thread(new Monitor());      monitor.setDaemon(true);      log("Startup thread resource keeping monitor for AspectJ.", false, RepositoryAspect.class);
          monitor.start();
        }    private class ResourcePair {      private final Object resource;      private final Exception scenario;      ResourcePair(Object resource) {
            if (resource == null) {
              throw new IllegalArgumentException("Target resource is null.");
            }        this.resource = resource;
            this.scenario = new Exception();
          }      public Object getResource() {
            return resource;
          }      public Exception getScenario() {
            return scenario;
          }      public int hashCode() {
            return resource.hashCode();
          }      public boolean equals(Object another) {
            if (another instanceof ResourcePair) {
              ResourcePair him = (ResourcePair) another;          return resource == him.resource;
            } else {
              return false;
            }
          }      public String toString() {
            StringBuffer buf = new StringBuffer();        buf.append(CRLF).append("<Resource instance=\"").append(resource).append("\">").append(CRLF);        StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);        scenario.printStackTrace(pw);        int p = 0;        String TAB = "\t\t\t";        StringBuffer stack = sw.getBuffer();
            
            while (p < stack.length()) {
              if (stack.charAt(p) == '\n') {
                stack.insert(p + 1, TAB);
                p += TAB.length();
              }
              p++;
            }        String msg = TAB + stack.toString();        buf.append("\t<open-scenario>").append(CRLF);
            buf.append("\t\t<![CDATA[").append(CRLF);
            buf.append(msg);
            buf.append(CRLF);
            buf.append("\t\t]]>").append(CRLF);
            buf.append("\t</open-scenario>");        buf.append(CRLF).append("</Resource>");        return buf.toString();
          }    }    private class Monitor implements Runnable {      public void run() {
            try {
              while (true) {
                Thread.sleep(interval);
                log("About to poll resources.", false, RepositoryAspect.class);            for (Iterator entries = items.entrySet().iterator(); entries.hasNext();) {              Map.Entry entry = (Map.Entry) entries.next();              Thread thread = (Thread) entry.getKey();
                  Set resources = (Set) entry.getValue();              if (!thread.isAlive()) {
                    if (!resources.isEmpty()) {
                      log(("Thread :" + thread + " is not alive, but it keeps " + resources.size() + " resources [")
                          .toUpperCase()
                          + resources + "]", true, Monitor.class);
                    } else {
                      entries.remove();
                    }
                  }
                }
              }
            } catch (InterruptedException e) {
              log("Monitor was interrupted.", e);
            }
          }
        }
      }}
      

  10.   

    非常感谢humanity提供了很好的自我监控连接池的思路,这样监控可能可以监控到用户代码级别,即取连接、执行、归还连接这个过程的调用情况,以及每个连接的使用时长和使用用途等。但可能监控不到dbcp自身的一些问题,比如说我们把连接归还给dbcp了,那么在空闲一段时间后,dbcp应该会将这个连接彻底关闭,或者又交给其他的请求了。也就是说,如果dbcp的管理机制存在问题,可能会出现,外部监听记录的连接池使用情况,与dbcp自己管理的连接池资源使用情况不一致。另外如果遇上数据库问题,导致的连接池使用效率很低,也不容易进行干预。研究dbcp发现用的是BasicDataSource 和 GenericObjectPool来管理的连接池。BasicDataSource中有个getConnectionInitSqls函数返回list of SQL statements。貌似可以打印出一个物理连接datasource正在执行的sql了。
    试验中  请有经验的人指教。
      

  11.   

    tomcat的dbcp 和commons的dbcp还是有区别的,
    tomcat的dbcp进行了自我封装集成放在了common/lib 下的naming-factory-dbcp-5.5.23.jar中,而其BasicDataSource中却没有getConnectionInitSqls函数。。
    真是比较郁闷。
    各位高手就没有人愿意发表一下看法?
      

  12.   

    DBCP连接池可能有些问题,你可以换用c3p0或者proxool