Java HTTP 请求中的内存泄漏

Java Memory Leak in HTTP requests

我有一个持续运行的 Java 应用程序。此应用程序向云服务器发出 HTTP 请求。问题是每次请求时,内存消耗都会增加,直到达到机器完全冻结的程度。我隔离了部分代码,我确定问题出在发出此 http 请求的代码块上。通过 prometheus / Grafana 分析 JVM 数字,我看到非堆内存(codecache 和元空间)的使用在不断增加,如图所示 here

在上图中,每当线路出现下降时,就是内存消耗达到 98% 时,Monit 会杀死应用程序。

导致这种内存消耗的方法如下(它执行了大约 300 次,直到它在初始化中耗尽了 1.5GB 多一点的可用内存)。

public AbstractRestReponse send(RestRequest request){
        BufferedReader in = null;
        OutputStream fout = null;
        URLConnection conn = null;
        InputStreamReader inputStreamReader = null;
        String result = "";
        try {
            MultipartEntityBuilder mb = MultipartEntityBuilder.create();// org.apache.http.entity.mime
            for (String key : request.getParams().keySet()) {
                String value = (String) request.getParams().get(key);
//                System.out.println(key + " = " + value);
                mb.addTextBody(key, value);
            }
            
            if (request.getFile() != null) {
                mb.addBinaryBody("file", request.getFile());
            }
            org.apache.http.HttpEntity e = mb.build();
    
            conn = new URL(request.getUrl()).openConnection();
            conn.setDoOutput(true);
            conn.addRequestProperty(e.getContentType().getName(), e.getContentType().getValue());// header "Content-Type"...
            conn.addRequestProperty("Content-Length", String.valueOf(e.getContentLength()));
            fout = conn.getOutputStream();
            e.writeTo(fout);// write multi part data...
            
            inputStreamReader = new InputStreamReader(conn.getInputStream());
            in = new BufferedReader(inputStreamReader);
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
            String text = result.toString(); 
            return objectMapper.readValue(text, FacialApiResult.class);
            
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            try {
                inputStreamReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                conn.getInputStream().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
        
    }

您似乎已经处理了 finally 块中所有可能的结束部分。无论如何,如果您的应用程序是 Java 7+ 上的 运行,最好使用 try-with 资源来安全地关闭所有 Closeable 对象。如果问题没有解决,这可能会进一步隔离问题。

想到

((HttpURLConnection)conn).disconnect()。字符串连接也是耗尽时间 and 的内存。删除换行符时有一个小错误。

NullPointerExceptions 可能会在 finally 块中出现,当由于异常而未达到打开时。但你应该检查一下。

public AbstractRestReponse send(RestRequest request) {
    URLConnection conn = null;
    try {
        MultipartEntityBuilder mb = MultipartEntityBuilder.create();// org.apache.http.entity.mime
        for (String key : request.getParams().keySet()) {
            String value = (String) request.getParams().get(key);
            mb.addTextBody(key, value);
        }

        if (request.getFile() != null) {
            mb.addBinaryBody("file", request.getFile());
        }
        org.apache.http.HttpEntity e = mb.build();

        conn = new URL(request.getUrl()).openConnection();
        conn.setDoOutput(true);
        conn.addRequestProperty(e.getContentType().getName(), e.getContentType().getValue());// header "Content-Type"...
        conn.addRequestProperty("Content-Length", String.valueOf(e.getContentLength()));
        try (fout = conn.getOutputStream()){
            e.writeTo(fout);// write multi part data...
        }
        
        StringBuilder resullt = new StringBuilder(2048);
        try (BufferedReader in = new InputStreamReader(conn.getInputStream(),
                StandardCharsets.UTF_8)) { // Charset
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line).append('\n'); // Newline
            }
        }
        String text = result.toString();
        return objectMapper.readValue(text, FacialApiResult.class);

    } catch (Exception e) {
        e.printStackTrace();
        return null;
    } finally {
        if (conn != null) {
            try {
                if (conn instanceof HttpURLConnection) {
                    ((HttpURLConnection) conn).disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace(); //Better logger
            }
        }
    }
    return null;
}
  • 我明确定义了字符集(UTF-8 可能是错误的)- 目前它是服务器的默认值。
  • 使用了 StringBuilder,并添加了缺失的换行符,这可能会导致错误的解析。
  • Try-with-resources 自动关闭,早一点。希望这不会破坏任何东西。
  • 当它是一个 HttpURLConnection 时断开连接。注意 instanceof 可能在单元测试模拟中发挥作用。