知识点编号:010
难度等级:⭐⭐⭐(必掌握)
面试频率:🔥🔥🔥🔥🔥
🎯 一句话总结
CLOSE_WAIT过多 = 你的代码忘记调用socket.close()了!💀
🤔 什么是CLOSE_WAIT?
四次挥手中的状态:
客户端 服务器
| FIN |
|---------------------->|
| CLOSE_WAIT ⚠️
| ACK |
|<----------------------|
| |
|(服务器应该close()) |
| |
| FIN |
|<----------------------|
CLOSED LAST_ACK
CLOSE_WAIT:
- 被动关闭方
- 收到FIN,发送ACK后进入
- 等待应用程序调用close()
- 如果不close(),一直停留!
⚠️ CLOSE_WAIT过多的原因
根本原因:代码没close()!
// ❌ 错误代码1:忘记关闭
Socket socket = new Socket("localhost", 8080);
InputStream in = socket.getInputStream();
// ... 使用socket ...
// 忘记socket.close()了!
客户端关闭 → 服务器收到FIN
服务器发送ACK → 进入CLOSE_WAIT
但是代码没有close() → 一直CLOSE_WAIT!
// ❌ 错误代码2:异常时没关闭
Socket socket = new Socket("localhost", 8080);
socket.getInputStream().read(); // 可能抛异常
socket.close(); // 异常时不执行!
// ❌ 错误代码3:HTTP客户端没关闭响应
CloseableHttpResponse response = httpClient.execute(request);
// 使用response...
// 忘记response.close()了!
💻 正确的代码写法
方案1:try-with-resources(最推荐)
// ✅ 自动关闭
try (Socket socket = new Socket("localhost", 8080);
InputStream in = socket.getInputStream()) {
// 使用socket
byte[] buffer = new byte[1024];
int len = in.read(buffer);
} // 自动调用close(),即使异常也会关闭
方案2:finally块
// ✅ 手动在finally中关闭
Socket socket = null;
try {
socket = new Socket("localhost", 8080);
// 使用socket...
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// 记录日志
}
}
}
方案3:Apache HttpClient正确用法
// ✅ 正确关闭响应
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("http://example.com");
try (CloseableHttpResponse response = httpClient.execute(request)) {
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
// 使用result...
} // response自动关闭
} // httpClient自动关闭
🔍 排查CLOSE_WAIT
1. 查看CLOSE_WAIT数量
# Linux
netstat -an | grep CLOSE_WAIT | wc -l
# 或
ss -ant | grep CLOSE-WAIT | wc -l
# 查看具体连接
netstat -anp | grep CLOSE_WAIT
# 输出:
# tcp 0 0 192.168.1.1:8080 192.168.1.2:50000 CLOSE_WAIT 12345/java
# 12345是进程PID
# /java是进程名
2. 定位问题代码
# 查看进程打开的文件描述符
lsof -p 12345 | grep TCP
# 使用jstack查看线程堆栈
jstack 12345 > thread.dump
# 分析thread.dump,找到:
# - 哪些线程持有Socket
# - 为什么没有关闭
3. 代码审查
搜索所有new Socket的地方:
grep -r "new Socket" .
检查每个地方是否:
1. 使用了try-with-resources
2. 或在finally中close()
3. 或使用连接池管理
🐛 常见面试题
Q1:CLOSE_WAIT过多是什么原因?怎么解决?
答案:
根本原因:
应用程序没有正确关闭socket
常见场景:
1. 忘记调用socket.close()
2. 异常处理不当,close()没执行
3. HTTP客户端忘记关闭响应
4. 连接池没有归还连接
排查步骤:
1. netstat -an | grep CLOSE_WAIT | wc -l
查看数量
2. netstat -anp | grep CLOSE_WAIT
找到进程PID
3. lsof -p PID
查看打开的文件描述符
4. jstack PID
查看线程堆栈
5. 代码审查
检查所有socket相关代码
解决方案:
1. ✅ 使用try-with-resources(Java 7+)
2. ✅ 在finally中关闭
3. ✅ 使用连接池
4. ✅ 定期代码审查
预防措施:
- 代码规范:强制使用try-with-resources
- 静态代码分析:FindBugs、SonarQube
- Code Review:检查资源泄漏
Q2:TIME_WAIT和CLOSE_WAIT的区别?
答案:
TIME_WAIT:
- 主动关闭方
- 四次挥手最后阶段
- 持续2MSL后自动消失
- 正常现象
- 过多 → 架构问题(短连接太多)
CLOSE_WAIT:
- 被动关闭方
- 收到FIN后进入
- 需要应用程序close()
- 不会自动消失
- 过多 → 代码问题(忘记close)
记忆方法:
TIME_WAIT:有时间限制,会自动消失
CLOSE_WAIT:等待关闭,需要代码处理
解决方法:
TIME_WAIT → 用长连接、连接池
CLOSE_WAIT → 修复代码,正确关闭
🎓 总结
CLOSE_WAIT的关键点:
- 原因:代码没调用close()
- 位置:被动关闭方
- 排查:netstat、lsof、jstack
- 解决:try-with-resources或finally
- 预防:代码规范、静态分析、Code Review
记忆口诀:
CLOSE_WAIT不会消 ⚠️
代码忘了调close 🚫
try-with-resources ✅
finally也能行 🛡️
代码规范很重要 📜
一句话记住:
CLOSE_WAIT = 你的Bug!
赶紧检查代码,加上close()!
文档创建时间:2025-10-31
作者:AI助手 🤖