线上内存泄漏排查全纪录之CloseableHttpAsyncClient

本文记录了一次线上内存泄漏排查的过程,重点在于RestClient使用CloseableHttpAsyncClient时,由于异常处理不当可能导致的内存泄漏。通过对源代码的深入分析,揭示了CloseableHttpAsyncClient的关闭流程,以及手动调用关闭的必要性,得出结论:自动资源释放已在HttpClient中实现,过早关闭可能适得其反。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

疑点restClient在报出异常时没有正确执行关闭导致内存泄漏

关键部分未改动前代码

于是果断修改代码如下,并重新上线

基于源代码分析

分析路线1:经过哪些调用,最终关闭了什么?

分析路线2:这种手动调用是否有必要,不会自动执行资源释放吗?

最终结论


前言

线上内存报警,重启之后内存占用降低,但是慢慢升高且几乎没有下降迹象,最终流量高峰时触发内存阈值,导致报警,前面通过内存排查工具已经进行过分析,今天从代码的角度的展开排查其中一点,以确定是否导致内存泄漏。

疑点restClient在报出异常时没有正确执行关闭导致内存泄漏

关键部分未改动前代码

     try {
			.....省略

			Response response = restClient.performRequest(HttpGet.METHOD_NAME, endpoint, headerMap);

			String result = EntityUtils.toString(response.getEntity());

			JSONObject data = JSON.parseObject(result);

			restClient.close();
			if(null != result && !result.isEmpty()) {
				return data.getJSONObject("_source");

			}
		}catch (Exception e){
			log.error("ESClient.get exception:{}", e.getMessage());
		}

其中restClient为org.elasticsearch.client.RestClient

线上异常:

初步分析:

如果执行

Response response = restClient.performRequest(HttpGet.METHOD_NAME, endpoint, headerMap);

那么,会导致try中的后面的语句无法执行,也就是说restClient.close();没有得到执行,导致内存泄漏。这个分析看似合理,

于是果断修改代码如下,并重新上线

     try {
			.....省略

			Response response = restClient.performRequest(HttpGet.METHOD_NAME, endpoint, headerMap);

			String result = EntityUtils.toString(response.getEntity());

			JSONObject data = JSON.parseObject(result);

			restClient.close();
			if(null != result && !result.isEmpty()) {
				return data.getJSONObject("_source");

			}
		}catch (Exception e){
			log.error("ESClient.get exception:{}", e.getMessage());
		}finally {
			try {
				restClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

上线之后,看似起了效果,但是有不那么明显,接下来需要仔细分析源代码了。

基于源代码分析

分析路线1:经过哪些调用,最终关闭了什么?

s1:调用到RestClient#close,内部是CloseableHttpAsyncClient#close

public class RestClient implements Closeable {

     private final CloseableHttpAsyncClient client;

    ...
    public void close() throws IOException {
        this.client.close();
    }
    ...
    
}

s2:实际调用到CloseableHttpAsyncClientBase#close,进一步调用到NHttpClientConnectionManager#shutdown

abstract class CloseableHttpAsyncClientBase extends CloseableHttpPipeliningClient {
    private final Log log = LogFactory.getLog(this.getClass());
    private final NHttpClientConnectionManager connmgr;
    private final Thread reactorThread;
    private final AtomicReference<CloseableHttpAsyncClientBase.Status> status;

    ......

    public void close() {
        if (this.status.compareAndSet(CloseableHttpAsyncClientBase.Status.ACTIVE, CloseableHttpAsyncClientBase.Status.STOPPED) && this.reactorThread != null) {
            try {
                this.connmgr.shutdown();
            } catch (IOException var3) {
                this.log.error("I/O error shutting down connection manager", var3);
            }

            try {
                this.reactorThread.join();
            } catch (InterruptedException var2) {
                Thread.currentThread().interrupt();
            }
        }

    }

    public boolean isRunning() {
        return this.status.get() == CloseableHttpAsyncClientBase.Status.ACTIVE;
    }

    static enum Status {
        INACTIVE,
        ACTIVE,
        STOPPED;

        private Status() {
        }
    }



}

此处补充一张CloseableHttpPipeliningClient继承关系图,便于理解其中的实现继承层次

s3: 实际调用到PoolingNHttpClientConnectionManager#shutdown,进一步调用到CPool#shutdown

@Contract(
    threading = Threadin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FeelTouch Labs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值