HttpURLConnection的套接字泄漏了。

5
我正在Linux上使用OpenJDK 11,并且需要确保所有通过HttpURLConnection完成的Web请求都被正确关闭,并且不保留任何文件描述符。Oracle的手册建议在InputStream上使用close,而Android的手册则建议在HttpURLConnection对象上使用disconnect。我还将Connection: closehttp.keepAlive设置为false,以避免连接池。这似乎适用于普通的HTTP请求,但不适用于响应发送了非分块编码的加密HTTPS请求。只有进行垃圾回收才能清除已关闭的连接。以下是示例代码:
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class Test {
    private static int printFds() throws IOException {
        int cnt = 0;
        try (Stream<Path> paths = Files.list(new File("/proc/self/fd").toPath())) {
            for (Path path : (Iterable<Path>)paths::iterator) {
                System.out.println(path);
                ++cnt;
            }
        }
        System.out.println();
        return cnt;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        System.setProperty("http.keepAlive", "false");
        for (int i = 0; i < 10; i++) {
            // Must be a https endpoint returning non-chunked response
            HttpURLConnection conn = (HttpURLConnection) new URL("https://www.google.com/").openConnection();
            conn.setRequestProperty("Connection", "close");
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            while (in.readLine() != null) {
            }
            in.close();
            conn.disconnect();
            conn = null;
            in = null;
        }

        Thread.sleep(1000);
        int numBeforeGc = printFds();

        System.gc();
        Thread.sleep(1000);
        int numAfterGc = printFds();
        System.out.println(numBeforeGc == numAfterGc ? "No socket leaks" : "Sockets were leaked");
    }
}

打印以下输出:
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/3
/proc/self/fd/4
/proc/self/fd/5
/proc/self/fd/9
/proc/self/fd/6
/proc/self/fd/7
/proc/self/fd/8
/proc/self/fd/10
/proc/self/fd/11
/proc/self/fd/12
/proc/self/fd/13
/proc/self/fd/14
/proc/self/fd/15
/proc/self/fd/16
/proc/self/fd/17
/proc/self/fd/18
/proc/self/fd/19

/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/3
/proc/self/fd/4
/proc/self/fd/5
/proc/self/fd/9
/proc/self/fd/6
/proc/self/fd/7
/proc/self/fd/8

Sockets were leaked

将URL改为HTTP后,套接字会按预期正确关闭,而不会触发垃圾回收:

/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/3
/proc/self/fd/4
/proc/self/fd/5
/proc/self/fd/6

/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/3
/proc/self/fd/4
/proc/self/fd/5
/proc/self/fd/6

No socket leak

已测试使用OpenJDK 11和12。我有什么遗漏或这是一个错误吗?


1
我尝试了5分钟的休眠,但没有任何区别。通过运行strace,我看到shutdown(fd,SHUT_RD),但没有close(直到GC)。 - Emil
@Tomer 给出相同的结果。 - Emil
顺便问一下,你是故意省略了URLConnection.connect()吗?(我查了API,应该要调用它)。另外,也尝试关闭输出流。API中指出:“在请求后调用URLConnection的InputStream或OutputStream上的close()方法可能会释放与此实例相关联的网络资源,除非特定协议规范为其指定了不同的行为。” - Tomer
有趣的是,问题不仅仅出在套接字上。HttpURLConnection本身也没有被垃圾回收。 - VGR
我认为它不会汇集连接,因为strace告诉我们当输入流已经读取完毕时,它会立即调用shutdown。我认为openjdk只是忘记了同时调用close。我还尝试将https.keepAlive设置为false,但没有任何区别。 - Emil
显示剩余11条评论
1个回答

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接