内存溢出的日常

事情是这样的,正开开心心的写着代码,突然线上报警了,内存告急,可用内存不足1G。

https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d086c417e1e496092bd2f7bc971615e~tplv-k3u1fbpfcp-watermark.image

???

第一反应是也没干啥啊,咋就报警了,然后运维同学抛出了这张图:

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/232a43472df5473d8c345f7ab8eca7cb~tplv-k3u1fbpfcp-watermark.image

线上应用的可用内存,就像中学边上那条长长的下坡路,急转直下…

不出意外是内存泄露了,80%还是我写的代码。

https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/18c7ad3c59214c8f9599fe54e95116ae~tplv-k3u1fbpfcp-watermark.image

抓紧联系运维同学,dump了两份间隔半小时的内存快照。

然后赶紧重启了服务器,先让它好起来。

接着开始用MAT分析快照,定位了是一些流没有被关闭,导致内存一直缓慢增长。

好了,到了这里才是今天的正文。

下面是错误写法,DefaultHttpClient也已经被标记成了@Deprecated,而且client和response也没有close。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static String sendGet(String url) {
    String res = null;
    try {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(url);
        HttpResponse response = client.execute(request);
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            res = EntityUtils.toString(response.getEntity());
        }
    }catch (IOException e) {
        logger.error("get请求提交失败:{}", url, e);
    }
    return res;
}

然后改成了这个样子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static String sendGet(String url) {
    String res = null;
    try (CloseableHttpClient client = HttpClients.createDefault()) {
        HttpGet request = new HttpGet(url);
        try (CloseableHttpResponse response = client.execute(request)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                res = EntityUtils.toString(response.getEntity());
            }
        }
    } catch (IOException e) {
        logger.error("get请求提交失败:{}", url, e);
    }
    return res;
}

问题到了这里,本来就完事了,突然想看看这个EntityUtils是干啥的?

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a842a85c4268409eac3cf59ac126f859~tplv-k3u1fbpfcp-watermark.image

一共这些方法,toString()已经知道了,那这个consume()是干啥的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Ensures that the entity content is fully consumed and the content stream, if exists,
 * is closed.
 *
 * @param entity the entity to consume.
 * @throws IOException if an error occurs reading the input stream
 *
 * @since 4.1
 */
public static void consume(final HttpEntity entity) throws IOException {
    if (entity == null) {
        return;
    }
    if (entity.isStreaming()) {
        final InputStream instream = entity.getContent();
        if (instream != null) {
            instream.close();
        }
    }
}

哦,等等,这是个关流的方法啊,我之前可是都没调用过啊,这不又漏了!!!

赶紧又去仔细看了看toString()

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f8c19d92b7554e9a9fbb51983be20840~tplv-k3u1fbpfcp-watermark.image

原来这里已经关闭过了,那没事了。