全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-11 8 分钟 ✍️ juanwangdev

Java连接泄漏检测与监控

数据库连接泄漏会导致连接池耗尽,系统无法响应,必须及时发现和预防。

连接泄漏原因

常见泄漏场景

Java
// 场景1:获取连接未关闭
Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 处理结果...
// 忘记关闭conn!连接一直占用

// 场景2:异常时未关闭
Connection conn = dataSource.getConnection();
try {
    Statement stmt = conn.createStatement();
    stmt.executeUpdate("INSERT INTO users VALUES(...)");
    // 异常发生,conn未关闭
} catch (SQLException e) {
    throw e;  // conn泄漏!
}

// 场景3:在循环中获取未关闭
for (User user : users) {
    Connection conn = dataSource.getConnection();
    // 处理...
    // 循环100次,泄漏100个连接
}

// 场景4:finally未正确关闭
Connection conn = dataSource.getConnection();
try {
    // ...
} finally {
    conn.close();  // close()可能抛异常!
    // 如果这里异常,后续清理不执行
}

正确的连接管理

try-with-resources

Java
// Java 7+ 推荐:自动关闭
try (Connection conn = dataSource.getConnection();
     PreparedStatement pstmt = conn.prepareStatement(sql);
     ResultSet rs = pstmt.executeQuery()) {

    while (rs.next()) {
        // 处理结果
    }
} // 自动关闭conn、pstmt、rs

try-finally嵌套

Java
// Java 6 或需要单独处理异常
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
    conn = dataSource.getConnection();
    pstmt = conn.prepareStatement(sql);
    rs = pstmt.executeQuery();

    while (rs.next()) {
        // 处理
    }
} catch (SQLException e) {
    throw e;
} finally {
    // 按顺序关闭,各自try-catch
    if (rs != null) try { rs.close(); } catch (SQLException e) {}
    if (pstmt != null) try { pstmt.close(); } catch (SQLException e) {}
    if (conn != null) try { conn.close(); } catch (SQLException e) {}
}

HikariCP泄漏检测

配置泄漏检测

Java
HikariConfig config = new HikariConfig();

// 泄漏检测阈值(毫秒)
config.setLeakDetectionThreshold(60000);  // 60秒

// 如果连接借用超过60秒未归还,记录警告日志

HikariDataSource dataSource = new HikariDataSource(config);

泄漏检测原理

Java
┌─────────────────────────────────────┐
│        HikariCP泄漏检测              │
├─────────────────────────────────────┤
│                                     │
│  1. 获取连接时记录时间戳              │
│                                     │
│  2. 连接归还时检查持有时间            │
│                                     │
│  3. 持有时间 > threshold             │
│     → 记录警告日志                   │
│     → 显示获取位置的堆栈              │
│                                     │
└─────────────────────────────────────┘

泄漏日志示例

Java
警告日志:
Connection leak detection triggered for connection...
Last connection acquisition trace:
    at com.example.UserService.getUser(UserService.java:25)
    at com.example.Controller.handle(Controller.java:10)
    ...

指出连接泄漏的代码位置

检测阈值设置

Java
// 设置建议
// 0:禁用泄漏检测
// >0:启用检测,超过阈值报警

// 开发环境:较小阈值,及时发现
config.setLeakDetectionThreshold(10000);  // 10秒

// 生产环境:适当阈值,避免误报
config.setLeakDetectionThreshold(120000);  // 2分钟

Druid泄漏检测

配置监控

Java
DruidDataSource dataSource = new DruidDataSource();

// 开启监控统计
dataSource.setFilters("stat");

// removeAbandoned:自动回收泄漏连接
dataSource.setRemoveAbandoned(true);        // 开启
dataSource.setRemoveAbandonedTimeout(180);  // 180秒回收
dataSource.setLogAbandoned(true);           // 记录日志

// 泄漏连接自动回收并记录堆栈

Druid监控页面

Java
// 配置StatViewServlet
@Bean
public ServletRegistrationBean druidStatViewServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(
        new StatViewServlet(), "/druid/*");
    return bean;
}

// 访问 /druid/datasource.html 查看:
// - ActiveCount:活跃连接数
// - PoolingCount:池中连接数
// - ErrorCount:错误次数
// - ExecuteCount:执行次数

连接池监控指标

HikariCP监控

Java
HikariPoolMXBean pool = dataSource.getHikariPoolMXBean();

// 关键指标
int active = pool.getActiveConnections();        // 活跃连接
int idle = pool.getIdleConnections();            // 空闲连接
int waiting = pool.getThreadsAwaitingConnection(); // 等待线程
int total = pool.getTotalConnections();          // 总连接

// 监控判断
if (waiting > 0) {
    // 有线程等待连接,池可能太小
}
if (active >= maxPoolSize) {
    // 活跃连接达到上限,可能有泄漏或池太小
}

JMX监控

Java
// JMX连接
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

ObjectName name = new ObjectName("com.zaxxer.hikari:type=Pool (MyPool)");
HikariPoolMXBean pool = JMX.newMXBeanProxy(mBeanServer, name, HikariPoolMXBean.class);

// 获取指标
int active = pool.getActiveConnections();

Micrometer/Prometheus监控

Java
// Spring Boot集成
@Bean
HikariDataSource dataSource(HikariConfig config) {
    return new HikariDataSource(config);
}

// 自动暴露指标到Prometheus
// hikaricp_connections_active
// hikaricp_connections_idle
// hikaricp_connections_pending
// hikaricp_connections_max

// Grafana可视化

自定义泄漏检测

连接包装器

Java
public class LeakDetectionConnection implements Connection {
    private Connection delegate;
    private long borrowTime;
    private String borrowStack;

    public LeakDetectionConnection(Connection delegate) {
        this.delegate = delegate;
        this.borrowTime = System.currentTimeMillis();
        this.borrowStack = Thread.currentThread().getStackTrace().toString();
    }

    @Override
    public void close() throws SQLException {
        long holdTime = System.currentTimeMillis() - borrowTime;
        if (holdTime > 60000) {  // 持有超过60秒
            log.warn("连接持有时间过长: {}ms, 堆栈: {}", holdTime, borrowStack);
        }
        delegate.close();
    }

    // 其他方法委托delegate...
}

连接池包装

Java
public class LeakDetectionDataSource implements DataSource {
    private DataSource delegate;

    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = delegate.getConnection();
        return new LeakDetectionConnection(conn);  // 包装检测
    }
}

定期巡检

检查活跃连接

YAML
// 定时检查活跃连接数
@Scheduled(fixedRate = 60000)
public void checkConnections() {
    HikariPoolMXBean pool = dataSource.getHikariPoolMXBean();

    int active = pool.getActiveConnections();
    int waiting = pool.getThreadsAwaitingConnection();

    if (active > threshold || waiting > 0) {
        log.warn("连接池异常: 活跃={}, 等待={}", active, waiting);
        // 发送告警
    }
}

检查连接池配置

text
// 检查配置是否合理
public void validatePoolConfig() {
    if (dataSource.getMaximumPoolSize() < expectedMax) {
        log.warn("连接池最大值太小");
    }
    if (dataSource.getConnectionTimeout() > 30000) {
        log.warn("连接超时设置过长");
    }
    if (dataSource.getLeakDetectionThreshold() == 0) {
        log.warn("泄漏检测未开启");
    }
}

监控告警

Prometheus告警规则

text
# alertmanager规则
groups:
  - name: datasource
    rules:
      - alert: HikariCPConnectionsExhausted
        expr: hikaricp_connections_active >= hikaricp_connections_max
        for: 1m
        annotations:
          summary: "连接池耗尽"

      - alert: HikariCPConnectionsPending
        expr: hikaricp_connections_pending > 0
        for: 30s
        annotations:
          summary: "有线程等待连接"

注意事项

所有获取的连接必须关闭(归还)

try-with-resources是最佳实践

HikariCP泄漏检测阈值不要太大(避免漏报)也不要太小(避免误报)

生产环境必须开启连接池监控

定期巡检连接池状态,及时发现异常

泄漏检测只是辅助,核心是规范编码

要点总结

  1. 连接泄漏:获取连接未归还,池耗尽
  2. try-with-resources自动关闭,最佳实践
  3. HikariCP leakDetectionThreshold检测泄漏
  4. Druid removeAbandoned自动回收泄漏连接
  5. 监控指标:active、idle、waiting,异常告警

📝 发现内容有误?点击此处直接编辑

← 上一篇 Java结果集映射与元数据
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库