You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Swing通过SSH隧道连接MySQL报JVM_Bind端口占用错误如何解决

问题解决方法

报错根因

你遇到的Address already in use: JVM_Bind报错核心是本地端口被占用,更换端口后仍报错的常见原因有两个:

  1. 代码每次调用conexion()方法都会新建一个SSH端口转发规则,且你没有主动释放SSH会话和端口绑定,旧的转发规则一直占用端口,多次调用后端口会被快速耗尽
  2. 硬编码指定本地端口,容易和系统其他进程/保留端口冲突

修复方案

1. 改造SSH隧道逻辑,让系统自动分配空闲端口,同时保留SSH会话用于后续资源释放

不要硬编码本地端口,调用JSch的端口转发方法时传入0作为本地端口参数,JSch会自动向系统申请可用的空闲端口并返回,完全避免端口冲突。同时要把SSH会话对象暴露出来,方便后续用完后关闭释放资源。

修改后的代码示例:

// 存储SSH会话和自动分配的本地端口的封装类,你也可以用其他方式传递这两个参数
static class SshTunnelInfo {
    Session sshSession;
    int allocatedLocalPort;

    public SshTunnelInfo(Session sshSession, int allocatedLocalPort) {
        this.sshSession = sshSession;
        this.allocatedLocalPort = allocatedLocalPort;
    }
}

private static SshTunnelInfo doSshTunnel(String strSshUser, String strSshPassword, String strSshHost,
                                         int nSshPort, String strRemoteHost, int nRemotePort) throws JSchException {
    final JSch jsch = new JSch();
    Session session = jsch.getSession(strSshUser, strSshHost, nSshPort);
    session.setPassword(strSshPassword);

    final Properties config = new Properties();
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);

    session.connect();
    // 传入0自动分配本地端口,返回值就是系统分配的可用端口
    int allocatedLocalPort = session.setPortForwardingL(0, strRemoteHost, nRemotePort);
    return new SshTunnelInfo(session, allocatedLocalPort);
}

// 注意:调用方拿到连接用完后,必须先关闭数据库连接,再关闭SSH会话
public static final Connection conexion(SshTunnelInfo[] tunnelInfoHolder) throws JSchException, SQLException {
    Connection con = null;
    SshTunnelInfo tunnelInfo = null;
    try {
        String strSshUser = "xxxx";
        String strSshPassword = "xxx";
        String strSshHost = "12.345.678.901";
        int nSshPort = 22;
        String strRemoteHost = "127.0.0.1";
        int nRemotePort = 3306;
        String strDbUser = "dbuser";
        String strDbPassword = "dbpw";
        String dbName = "dbname";

        tunnelInfo = doSshTunnel(strSshUser, strSshPassword, strSshHost, nSshPort, strRemoteHost, nRemotePort);
        // 把隧道信息传给调用方,方便后续释放资源
        if (tunnelInfoHolder != null && tunnelInfoHolder.length > 0) {
            tunnelInfoHolder[0] = tunnelInfo;
        }

        Class.forName("com.mysql.cj.jdbc.Driver");
        con = DriverManager.getConnection("jdbc:mysql://localhost:" + tunnelInfo.allocatedLocalPort + "/" + dbName, strDbUser, strDbPassword);

    } catch (ClassNotFoundException e) {
        // 出现异常要主动关闭已经创建的SSH会话,避免资源泄漏
        if (tunnelInfo != null && tunnelInfo.sshSession.isConnected()) {
            tunnelInfo.sshSession.disconnect();
        }
        e.printStackTrace();
    } catch (JSchException | SQLException e) {
        if (tunnelInfo != null && tunnelInfo.sshSession.isConnected()) {
            tunnelInfo.sshSession.disconnect();
        }
        e.printStackTrace();
    }
    return con;
}

2. 正确释放资源

拿到数据库连接完成操作后,必须按顺序释放资源:

  1. 先关闭数据库Connection连接
  2. 再关闭SSH会话,端口会自动释放
    示例调用逻辑:
// 调用示例
SshTunnelInfo[] tunnelHolder = new SshTunnelInfo[1];
Connection conn = null;
try {
    conn = conexion(tunnelHolder);
    // 这里写你的数据库操作逻辑
} finally {
    // 先关数据库连接
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    // 再关SSH隧道
    if (tunnelHolder[0] != null && tunnelHolder[0].sshSession.isConnected()) {
        tunnelHolder[0].sshSession.disconnect();
    }
}

3. 额外排查项

如果修改后还有问题,可以做以下排查:

  • 检查本地安全软件、VPN是否限制了端口绑定,不要使用1024以下的特权端口
  • Windows系统可以执行命令netstat -ano | findstr "端口号"查看端口是否被其他进程占用,Linux/macOS执行lsof -i :端口号排查

内容的提问来源于stack exchange,提问作者Newbie83

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 23:24:00