开两个服务内存溢出_应用服务OkHttpClient创建大量对外连接时内存溢出

d24f32f169926b00192ef41d3da58a8e.png

1 背景

最近工作中碰到一个生产问题,就是应用服务在使用 OkHttpClient 时,在创建大量对外连接时线程堆积导致内存溢出。

主要表现是在流量极低的情况下,即平均 qps 在 1~4 左右的情况下,各主要线程都很低,但是系统活跃线程却很高,超过了限制的阈值,如果持续下去,线程堆积过高则会导致应用程序直接挂掉。

2 排查

​2.1 原因

在对应用服务的其中一个 pod 的线程栈 dump 出来分析后,发现 dump 出来的文件中,有 266 个线程,其中 145 个都来自于同一个ConnectionPool(OkHttp ConnectionPool),而且是在流量不高的情况下。

经分析主要原因是应用中在创建 OkHttpClient 对象时,没有创建同一个 OkHttpClient 实例并重复使用,而是对于所有的 http 请求都重复创建一个新的实例,而每个实例都有自己的连接池和线程池,从而导致线程大量堆积。

92ee9cc0653e50dc7e0416873d4415cf.png

而 OkHttpClient 默认的最大线程空闲数是 5,keepAlive 时间为 5 分钟,也就是发起一次网络连接后,5 分钟内不会断开连接,从而导致在创建大量对外连接时内存溢出。

dc8b3aba3538a20d673090559c53bd24.png

而查看 OkHttpClient 类的源码发现,通过 OkHttp 创建每个OkHttpClient 实例的时候,每个客户端都会持有自己的连接池和线程池。

对于通过 OkHttp 创建的所有的 http 请求,在创建一个 OkHttpClient 实例并重复使用时,重用它的连接池和线程池可以减少延迟和节省内存。

c090ee60dbc0ca05b620f78dd9cb5dd1.png

2.2 验证过程

2.2.1 修改前

查看应用程序中的代码可以看到,在创建 OkHttpClient 实例的时候,并没有创建一个可复用的实例,而是每次创建 http 请求时,都会 new 一个新的 OkHttpClient 实例。

65a942886a62a9558f94d9aa66bb6a4a.png

在 jmeter 模拟多个线程同时请求应用服务的接口,每次请求 300 个线程,通过多次请求之后,通过 jconsole 工具可以很清楚的看到线程数刚开始的时候基本为 0,但多次模拟请求过后很快就超过了 3000 个,然后电脑连续挂了 2 次。。。

67ec073237088f18ec26e78a086b4923.png

并且从下图中,我们可以非常明显地看到,这些线程大部分都是 OkHttp ConnectionPool。

9dfcab56a970ba3b6d3be245f57d1b87.png

2.2.2 修改后

修改之后,创建一个共享的 OkHttpClient 对象,jmeter 每次模拟 300 请求,通过 jconsole 可以观察到,发现第一次模拟达到了一个峰值后,后面无论再怎么模拟,峰值基本都不再改变了。

而修改之前每次模拟 300 请求,模拟几次之后会发现线程数一直在蹭蹭往上涨,直至线程大量堆积导致应用程序崩溃。

ecbfcac115a701cdbb59241aed478ebd.png

3 解决

创建一个共享的 OkHttpClient 对象即可,而不是每次调用方法都单独创建一个 OkHttpClient 对象,每个 OkHttpClient 对象都有自己的连接池和线程池,会导致大量的线程堆积,从而可能会导致程序崩溃。

d0d0886374a1180e81f20cb68eecd509.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值