Java Swing通过SSH隧道连接MySQL报JVM_Bind端口占用错误如何解决
问题解决方法
报错根因
你遇到的Address already in use: JVM_Bind报错核心是本地端口被占用,更换端口后仍报错的常见原因有两个:
- 代码每次调用
conexion()方法都会新建一个SSH端口转发规则,且你没有主动释放SSH会话和端口绑定,旧的转发规则一直占用端口,多次调用后端口会被快速耗尽 - 硬编码指定本地端口,容易和系统其他进程/保留端口冲突
修复方案
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. 正确释放资源
拿到数据库连接完成操作后,必须按顺序释放资源:
- 先关闭数据库Connection连接
- 再关闭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
相关产品推荐
相关产品推荐

