HttpURLConnection 的套接字泄漏

Sockets of HttpURLConnection are leaked

我在 Linux 上使用 OpenJDK 11,我需要确保使用 HttpURLConnection 完成的所有 Web 请求都正确关闭,并且不要让任何文件描述符保持打开状态。

Oracle's manual tells to use close on the InputStream and Android's manual 告诉在 HttpURLConnection 对象上使用 disconnect

我还将 Connection: closehttp.keepAlive 设置为 false 以避免连接池。

这似乎适用于纯 http 请求,但未加密 https 请求,其响应是 non-分块编码。似乎只有 GC 会清理已关闭的连接。

此示例代码:

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

更改为 http URL 可以在没有 GC 的情况下按预期正确关闭套接字:

/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 进行了测试。我是否遗漏了什么或者这是一个错误?

原来是个bug:https://bugs.openjdk.java.net/browse/JDK-8216326

shutdownInput 现在在 JDK 11 和 13(但不是 12)的最新版本中被 close 取代。