tomcat关闭应用时的清理工作(1): JDBC Driver

tomcat在关闭应用时,对资源做了一些清理,避免了泄露,这个工作主要是WebappClassLoader里做的,WebappClassLoader也实现自Lifecycle接口,在应用关闭时,会触发其stop方法:

@Override
public void stop() throws LifecycleException {
    // Clearing references should be done before setting started to
    // false, due to possible side effects
    clearReferences();
    ......
}

释放引用的工作主要在clearReferences里:

protected void clearReferences() {

    // De-register any remaining JDBC drivers
    clearReferencesJdbc();

    // Stop any threads the web application started
    clearReferencesThreads();

    // Check for leaks triggered by ThreadLocals loaded by this class loader
    checkThreadLocalsForLeaks();

    // Clear RMI Targets loaded by this class loader
    clearReferencesRmiTargets();

    // Null out any static or final fields from loaded classes,
    // as a workaround for apparent garbage collection bugs
    if (clearReferencesStatic) {
        clearReferencesStaticFinal();
    }

     // Clear the IntrospectionUtils cache.
    IntrospectionUtils.clear();

    // Clear the classloader reference in common-logging
    if (clearReferencesLogFactoryRelease) {
        org.apache.juli.logging.LogFactory.release(this);
    }

    // Clear the resource bundle cache
    // This shouldn't be necessary, the cache uses weak references but
    // it has caused leaks. Oddly, using the leak detection code in
    // standard host allows the class loader to be GC'd. This has been seen
    // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl?
    clearReferencesResourceBundles();

    // Clear the classloader reference in the VM's bean introspector
    java.beans.Introspector.flushCaches();

}

其中对JDBC Driver的清理,是clearReferencesJdbc方法,它检查当前WebappClassLoader加载过的,在关闭时未注销掉的JDBC Driver,给出警告信息,并强行将这些Driver反注册掉。所以我们有时在关闭时会看到类似下面的信息:

SEVERE: The web application [xxx] registered the JDBC driver 
[com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. 
To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

我们通过下面的例子来模拟一下这个现象:

@WebServlet(name = "MainServlet", urlPatterns = { "/main" }, loadOnStartup=1)
public class MainServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public void init() {
        try {
            Class.forName("com.alibaba.druid.mock.MockDriver");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        PrintWriter wr = resp.getWriter();
        wr.write("ok");
        wr.flush();
    }
}

上面的servlet在初始化时注册了一个Driver,但销毁时未将这个Driver给反注册掉;这时不管是显式的通过命令来stop tomcat,还是因为设置了自动reload,而且恰好检查到应用有变,执行了reload的时候(reload也是对app context进行stop,然后再重新start),就会被tomcat判断为泄露,给出警告并强制反注册Driver:

$ kill `pidof java`

// 在日志里会看到
严重: The web application [] registered the JDBC driver [com.alibaba.druid.mock.MockDriver]
but failed to unregister it when the web application was stopped. 
To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

要避免这个信息,应用或框架应该自己来保证在销毁时将JDBC Driver反注册掉。例如在destroy方法里:

@Override
public void destroy() {
    super.destroy();
    try{
        DriverManager.deregisterDriver(DriverManager.getDrivers().nextElement());
    }catch(Exception e){
        e.printStackTrace();
    }
}

因为tomcat自带了DBCP数据库连接池,很多用户在使用DBCP时遇到了这个问题,并建议在 DBCP 的 BasicDataSourceclose方法里执行反注册驱动的行为来解决这个警告。但DBCP的开发者认为这个应该是使用者的责任,不愿意接受这种建议,参考这里

发表评论

电子邮件地址不会被公开。 必填项已用*标注