Deep Dive into Druid Connection Pool: Initialization, Retrieval, and Recycling Explained

This technical guide breaks down Alibaba's Druid JDBC connection pool, detailing its initialization process, how connections are fetched and returned, the internal threads and condition‑signal coordination, execution handling, recommended configurations, and monitoring integration, all illustrated with code snippets and diagrams.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Deep Dive into Druid Connection Pool: Initialization, Retrieval, and Recycling Explained

Introduction

This document explains the internal mechanisms of Alibaba's Druid connection pool and provides concrete configuration guidance for troubleshooting common database‑related issues such as connection starvation, long‑running holds, and performance overhead.

Overview of Druid

Druid creates a fixed set of physical java.sql.Connection objects at application startup and stores them in a pool. When a request arrives, a connection is taken from the pool; when the request finishes, the connection is returned, similar to a shared‑bike system.

Resource reuse : avoids the cost of repeatedly creating and destroying connections.

Performance boost : connections are ready for immediate use, reducing latency.

Optimized allocation : maxActive limits the total number of connections per application, preventing a single service from monopolising the database.

Connection management : idle‑timeouts and forced reclamation reduce the risk of leaks.

Druid pool concept illustration
Druid pool concept illustration

Initialization Process (init())

The pool is initialized the first time getConnection() is called or when init() is invoked directly.

Create initialSize physical connections.

Start three internal daemon threads: LogStatsThread – periodically logs pool statistics. CreateConnectionThread – creates new connections when the pool is empty. DestroyConnectionThread – scans idle connections and destroys or validates them.

LogStatsThread

If timeBetweenLogStatsMillis > 0, the thread prints a statistics log at that interval.

CreateConnectionThread

Listens for the empty condition signal. When signalled and the pool size is below maxActive, it creates a new physical connection.

if (activeCount + poolingCount >= maxActive) {
    empty.await();
    continue;
}

Connection creation logic (simplified):

public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
    Connection conn = createPhysicalConnection(url, physicalConnectProperties);
    // initialise, validate, and wrap
    return new PhysicalConnectionInfo(conn, ...);
}

Putting a connection back into the pool:

private boolean put(DruidConnectionHolder holder, long createTaskId) {
    lock.lock();
    try {
        if (poolingCount >= maxActive) return false;
        connections[poolingCount] = holder;
        incrementPoolingCount();
        notEmpty.signal();
        notEmptySignalCount++;
        return true;
    } finally {
        lock.unlock();
    }
}

DestroyConnectionThread

Runs every timeBetweenEvictionRunsMillis and performs the following checks on each idle connection:

If the physical lifetime exceeds phyTimeoutMillis, destroy it.

If keepAlive is true and idle time ≥ keepAliveBetweenTimeMillis, perform a keep‑alive validation.

If idle time ≥ minEvictableIdleTimeMillis, destroy the connection but keep at least minIdle connections.

If idle time ≥ maxEvictableIdleTimeMillis, force destruction.

If removeAbandoned is true and the borrowed time exceeds removeAbandonedTimeoutMillis, forcibly close the statement and return the connection.

public void run() {
    shrink(true, keepAlive);
    if (isRemoveAbandoned()) {
        removeAbandoned();
    }
}

Getting a Connection (getConnection())

The core method is getConnectionDirect(long maxWaitMillis). It repeatedly attempts to obtain a connection, validates it if testOnBorrow or testWhileIdle is enabled, and records borrowing information for leak detection.

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
    for (;;) {
        DruidPooledConnection pc = getConnectionInternal(maxWaitMillis);
        if (testOnBorrow) {
            boolean valid = testConnectionInternal(pc.holder, pc.conn);
            if (!valid) {
                discardConnection(pc.holder);
                continue;
            }
        }
        if (removeAbandoned) {
            pc.connectStackTrace = Thread.currentThread().getStackTrace();
            pc.setConnectedTimeNano();
            activeConnectionLock.lock();
            try { activeConnections.put(pc, PRESENT); }
            finally { activeConnectionLock.unlock(); }
        }
        return pc;
    }
}

getConnectionInternal()

Fetches a holder from the pool (or waits on notEmpty) and wraps it in a DruidPooledConnection. It also respects maxWaitThreadCount to avoid excessive waiting threads.

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
    long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
    for (;;) {
        DruidConnectionHolder holder = (maxWait > 0) ? pollLast(nanos) : takeLast();
        holder.incrementUseCount();
        return new DruidPooledConnection(holder);
    }
}

takeLast()

public DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
    while (poolingCount == 0) {
        emptySignal();
        notEmptyWaitThreadCount++;
        try { notEmpty.await(); } finally { notEmptyWaitThreadCount--; }
    }
    decrementPoolingCount();
    DruidConnectionHolder last = connections[poolingCount];
    connections[poolingCount] = null;
    return last;
}

At this point a usable DruidPooledConnection is returned to the caller.

Execution and Exception Handling

When MyBatis invokes SqlSessionTemplate$SqlSessionInterceptor.invoke(), it eventually calls DruidPooledPreparedStatement.execute(). If an exception occurs, checkException() forwards the error to DruidDataSource.handleConnectionException(), which uses exceptionSorter to decide whether the exception is fatal and may invoke handleFatalError() to destroy the connection.

public boolean execute() throws SQLException {
    conn.beforeExecute();
    try {
        return stmt.execute();
    } catch (Throwable t) {
        errorCheck(t);
        throw checkException(t);
    } finally {
        conn.afterExecute();
    }
}

handleConnectionException()

public void handleConnectionException(DruidPooledConnection pc, Throwable t, String sql) throws SQLException {
    if (t instanceof SQLException) {
        SQLException sqlEx = (SQLException) t;
        if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) {
            handleFatalError(pc, sqlEx, sql);
        }
        throw sqlEx;
    } else {
        throw new SQLException("Error", t);
    }
}

Recycling a Connection (recycle())

After SQL execution, DruidPooledConnection.recycle() delegates to DruidDataSource.recycle(), which resets the physical connection, optionally validates it ( testOnReturn), checks usage count and physical lifetime, and finally puts the holder back into the pool with a notEmpty signal.

public void recycle() throws SQLException {
    DruidConnectionHolder holder = this.holder;
    DruidAbstractDataSource ds = holder.getDataSource();
    ds.recycle(this);
}

DruidDataSource.recycle()

protected void recycle(DruidPooledConnection pc) throws SQLException {
    DruidConnectionHolder holder = pc.holder;
    Connection physical = holder.conn;
    try {
        holder.reset();
        if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
            discardConnection(holder);
            return;
        }
        if (testOnReturn) {
            // optional validation logic
        }
        if (phyTimeoutMillis > 0 && System.currentTimeMillis() - holder.connectTimeMillis > phyTimeoutMillis) {
            discardConnection(holder);
            return;
        }
        lock.lock();
        try {
            boolean result = putLast(holder, System.currentTimeMillis());
            recycleCount++;
            if (!result) JdbcUtils.close(holder.conn);
        } finally {
            lock.unlock();
        }
    } catch (Throwable e) {
        // omitted error handling
    }
}

putLast()

boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
    if (poolingCount >= maxActive || e.discard) return false;
    e.lastActiveTimeMillis = lastActiveTimeMillis;
    connections[poolingCount] = e;
    incrementPoolingCount();
    notEmpty.signal();
    notEmptySignalCount++;
    return true;
}

The connection is now back in the pool and can be reused by other threads.

Core Operations Summary

init() : creates initialSize connections and starts the three background threads.

getConnection() : removes a connection from the pool, optionally validates it, and records borrowing information for leak detection.

recycle() : returns the connection to the pool, optionally validates it again, and signals waiting threads.

Condition‑Signal Coordination

When the pool is empty, a request thread signals empty and waits on notEmpty. CreateConnectionThread reacts, creates a new connection if the pool size permits, and signals notEmpty.

When a connection is recycled, notEmpty is signalled, waking any waiting request threads.

Detection and Destruction Logic

Borrowing: optional testOnBorrow and testWhileIdle validation.

Execution: fatal SQL exceptions trigger handleFatalError().

Returning: checks for phyMaxUseCount, testOnReturn, and phyTimeoutMillis.

DestroyConnectionThread: periodic scans enforce minEvictableIdleTimeMillis, maxEvictableIdleTimeMillis, keepAlive, and removeAbandoned policies.

Common and Recommended Configuration

Typical XML Bean

<bean id="userdataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="name" value="userdataSource"/>
    <property name="url" value="${userdataSource_url}"/>
    <property name="driverClassName" value="com.zhuanzhuan.arch.kms.jdbc.mysql.Driver"/>
    <property name="initialSize" value="1"/>
    <property name="minIdle" value="3"/>
    <property name="maxActive" value="20"/>
    <property name="maxWait" value="60000"/>
    <property name="validationQuery" value="SELECT 1"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testWhileIdle" value="true"/>
    <property name="testOnReturn" value="false"/>
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>

Interpretation of the Settings

At startup one connection is created; the pool keeps at least three idle connections and never exceeds twenty total connections.

If no idle connection is available, a request waits up to 60 seconds ( maxWait).

Idle connections older than 60 seconds are validated using validationQuery before being handed out ( testWhileIdle).

Fatal MySQL exceptions (e.g., CommunicationsException) cause the offending connection to be destroyed via handleFatalError().

The DestroyConnectionThread runs every 60 seconds, discarding connections idle longer than 5 minutes (while preserving minIdle) and forcibly destroying those idle beyond the default 7‑hour maximum.

Monitoring

Druid exposes SPI extension points. By implementing custom hooks, metrics such as pool size, active connections, wait times, and error counts can be exported to Prometheus for real‑time visibility.

Prometheus metrics dashboard
Prometheus metrics dashboard
Pool usage chart
Pool usage chart
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavamonitoringperformancedatabaseConfigurationConnection PoolDruid
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.