Короткий ответ: полностью «гарантировать» мгновенное освобождение ресурсов операционной системы нельзя. close() — это best‑effort вызов, и при проблемах с сетевым стеком или ОС он может бросить IOException или вовсе не освободить всё «здесь и сейчас». Но есть набор практик, которые максимально надёжно закрывают сокет с корректной обработкой ошибок.
Что важно знать
- close() по спецификации идемпотентен — повторный вызов на уже закрытом сокете либо ничего не сделает, либо бросит исключение, но ресурсы уже в основном освобождены.
- Закрытие InputStream/OutputStream, полученных от Socket, в большинстве реализаций приводит к закрытию сокета. Но эти методы тоже могут бросать IOException.
- Если нужно принудительно разорвать соединение и освободить дескриптор немедленно, можно использовать SO_LINGER с таймаутом 0 (abortive close — RST), но это влияет на протокол (потеря незакрытых данных на удалённой стороне).
- Нельзя «выжать» у JVM/OS больше, чем ей позволяет платформа. В некоторых ошибках закрытие может оказаться неудачным — логировать и освобождать все ссылки (чтобы GC мог очистить) — реальная стратегия.
Рекомендации и паттерны
1) Используйте try-with-resources (или finally) для Socket/ServerSocket/stream:
- это гарантирует попытку закрытия даже при исключениях.
2) Перед закрытием для корректного завершения протокола:
- по возможности вызовите socket.shutdownOutput() (и при необходимости shutdownInput()), чтобы сказать клиенту «я закончил передачу» и дать время на корректное завершение.
3) Если нужно быстро освободить дескриптор (даёт RST, теряет данные):
- socket.setSoLinger(true, 0);
- затем socket.close();
Учтите, что setSoLinger может бросить SocketException, и установка имеет смысл только до close().
4) В catch при close():
- не пытайтесь бесконечно ретраить; ретрай один раз можно сделать при transient‑ошибке (с небольшим ожиданием), но это не гарантирует успех.
- логируйте исключение и освобождайте ссылки (socket = null).
- не пытайтесь использовать рефлексию, private implClose и т.п. — это ненадёжно и небезопасно.
Пример шаблона (упрощённый):
try (Socket socket = serverSocket.accept()) {
try (InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream()) {
// работа с потоками
} catch (IOException e) {
// обработка ошибок при работе со стримами
} finally {
// корректное завершение протокола, если возможно
try {
socket.shutdownOutput();
} catch (IOException ignored) {}
// опционально: форсированный разрыв — опасно, убирает гарантии доставки
try {
socket.setSoLinger(true, 0);
} catch (SocketException ignored) {}
try {
socket.close();
} catch (IOException e) {
// логируем и отказываемся от дальнейших попыток
logger.warn("Failed to close socket", e);
}
}
}
Что именно писать в catch(IOException) после socket.close()?
- Не пытайтесь «гарантировать» закрытие повторными вызовами в цикле — это бессмысленно в случае серьёзных проблем уровня ОС.
- Адекватная логика: логирование (warn/error), освобождение ссылок и, при необходимости, уведомление мониторинга/оператора.
- Можно сделать одно дополнительное попытку close() или установить SO_LINGER(0) перед повторной попыткой, но и это не даст абсолютной гарантии.
Ещё несколько практических советов
- Для long‑running серверов используйте таймауты (SO_TIMEOUT) или неблокирующие/асинхронные API, чтобы не зависать в блокирующих вызовах.
- Управляйте потоками, которые работают с сокетом: при закрытии сокета корректно прерывайте/сигнализируйте рабочим потокам, чтобы они сами закрыли ресурсы.
- Логируйте контекст (remote addr, state, причина закрытия), чтобы при проблемах понять, почему close() упал.
- Если на вашей платформе встречаются частые «зависания» close(), возможно, это багы OS/driver — рассмотрите обновление/патчи.
Итог
Полной гарантии «здесь и сейчас» дать нельзя. Действуйте по best‑practices: try-with-resources/finally, корректный shutdownOutput(), в особых случаях setSoLinger(true, 0) для немедленного RST, логирование и отказ от бесконечных ретраев. Это максимально надёжный и корректный подход на уровне Java-приложения.