c3p0 연결 풀을 사용하는 애플리케이션이 Tomcat 종료 시 다음과 같은 경고를 출력합니다:
경고: 웹 애플리케이션 [uavmonitor]이(가) [Timer-0] 스레드를 시작했지만 중지하지 못했습니다.
이로 인해 메모리 누수가 발생할 가능성이 매우 높습니다.
경고: 웹 애플리케이션 [uavmonitor]이(가) [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0]
스레드를 시작했지만 중지하지 못했습니다.
이 문제는 c3p0가 생성한 백그라운드 스레드가 Tomcat 종료 시 적절하게 정리되지 않아 발생합니다. c3p0 공식 문서에 따르면,
라이브러리는 내부적으로 헬퍼 스레드와 java.util.Timer 스레드를 생성합니다.
연결 풀 관리 클래스 예시
public class ConnectionManager {
private static PooledDataSource connectionPool;
static {
try {
connectionPool = new ComboPooledDataSource();
Properties config = loadDatabaseProperties();
connectionPool.setDriverClass(config.getProperty("db.driver"));
connectionPool.setJdbcUrl(config.getProperty("db.url"));
connectionPool.setUser(config.getProperty("db.user"));
connectionPool.setPassword(config.getProperty("db.password"));
connectionPool.setInitialPoolSize(5);
connectionPool.setMinPoolSize(5);
connectionPool.setMaxPoolSize(20);
connectionPool.setIdleConnectionTestPeriod(10);
connectionPool.setTestConnectionOnCheckin(true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static DataSource getConnectionPool() {
return connectionPool;
}
}
해결 방법: ServletContextListener 구현
커넥션 풀을 명시적으로 종료하는 리스너를 추가합니다:
public class ConnectionCleanupListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent event) {
try {
DataSource ds = ConnectionManager.getConnectionPool();
if (ds != null) {
DataSources.destroy(ds);
System.out.println("데이터베이스 연결 풀 정상 종료");
}
} catch (SQLException sqlEx) {
sqlEx.printStackTrace();
}
}
@Override
public void contextInitialized(ServletContextEvent event) {}
}
web.xml에 리스너 등록:
<listener>
<listener-class>com.example.ConnectionCleanupListener</listener-class>
</listener>
참고: 애플리케이션에서 직접 생성한 ExecutorService가 있을 경우, 동일한 리스너 내에서 shutdownNow()를 호출해 종료해야 합니다.