Search code examples
javamemory-leaksurlconnection

Java Memory Leak in HTTP requests


I have a Java application that is runs constantly. This application makes HTTP requests to a cloud server. The problem is that at each request the memory consumption increases until it reaches the point that the machine complete freezes. I isolated parts of the code and I'm sure the problem is with this code block making this http requests. Analyzing the JVM numbers, via prometheus / Grafana, I see that the use of non-heap memory (codecache and metaspace) are constantly increasing, as shown here

In the image above, whenever there is a drop in the line, it is when 98% of memory consumption reached, and Monit kills the app.

The method that is causing this memory consumption, is below (it is executed approx. 300 times until it exhausts a little more than 1.5GB of available memory in the initialization).

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();
            }
            
        }
        
    }

Solution

  • ((HttpURLConnection)conn).disconnect() comes to mind. Also String concatenation is time and memory exhaustive. And there was a minor bug in dropping newlines.

    NullPointerExceptions may arise in the finally block when an open was not reached due to an exception. But you should have checked that.

    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;
    }
    
    • I explicitly defined the charset (UTF-8 might be wrong) - momentarily it is the server's default.
    • Used a StringBuilder, and added the missing newline, which might have lead to wrong parsing.
    • Try-with-resources for auto-closing, and a bit earlier. Hopefully this does not break anything.
    • Disconnecting the connection when it is an HttpURLConnection. Mind the instanceof which might play a role in unit tests mocking.