当前位置: 首页 > news >正文

okhttp导致的内存溢出(OOM)sun.security.ssl.SSLSocketImpl

  • 使用分析工具:MAT(Memory Analyzer Tool)、JvisualVM
  • 占用内存:sun.security.ssl.SSLSocketImpl

一、 项目场景:

功能:一个定时任务(xxl-job)采用线程池的方式多线程请求第三方拉取数据,网络框架使用okhttp3。
问题:执行job时,内存短时间内暴增,导致OOM


二、问题描述

  • 定时任务执行时,突然内存激增,OOM导致项目重启。
  • 下面这张图是重启后再次执行定时任务的内存监控

    image.png

三、原因分析:

3.1 查看堆栈信息

使用MAT查看堆栈信息,sun.security.ssl.SSLSocketImpl这个东西占了62%
image.png

点击Details ,可以看到有9k多个对象

image.png

使用OQL查询sun.security.ssl.SSLSocketImpl,发现其中的host都是请求第三方的地址

select * from sun.security.ssl.SSLSocketImpl

image.png
image.png

到这里,基本可以定位到是由于请求第三方资源没有释放,导致内存暴增。接下来查看请求第三方的代码

3.2 查看代码

看到底层工具类OkHttpClientUtil工具类中获取OkHttpClient对象的代码是这样的,每次请求都是new一个OkhttpClient对象,可能是每次都是new一个OkhttpClient的问题,于是在本地复现

   private static OkHttpClient getHttpClient() {return new OkHttpClient.Builder().connectTimeout(obtainConnectTimeOut(), TimeUnit.MILLISECONDS).writeTimeout(obtainWriteTimeOut(), TimeUnit.MILLISECONDS).readTimeout(obtainReadTimeOut(), TimeUnit.MILLISECONDS).build();}

四、场景复现:

模拟生产,采用线程池方式多线程请求,请求地址改为百度,数据随便塞一点只要正常相应就行。

4.1代码

OkHttpClientUtil 工具类,getHttpClient()是之前的,getHttpClientSingleton()是我新写的

@Slf4j
public class OkHttpClientUtil {private static final MediaType TYPE_JSON = MediaType.parse("application/json; charset=utf-8");private volatile static OkHttpClient okHttpClient;public static OkHttpClient getHttpClient() {return new OkHttpClient.Builder().connectTimeout(30000, TimeUnit.MILLISECONDS).writeTimeout(1800000, TimeUnit.MILLISECONDS).readTimeout(1800000, TimeUnit.MILLISECONDS).build();}/*** 单例双重检测** @return*/public static OkHttpClient getHttpClientSingleton() {if (null == okHttpClient) {synchronized (OkHttpClient.class) {if (null == okHttpClient) {okHttpClient = new OkHttpClient.Builder().connectTimeout(30000, TimeUnit.MILLISECONDS).writeTimeout(1800000, TimeUnit.MILLISECONDS).readTimeout(1800000, TimeUnit.MILLISECONDS).build();}}}return okHttpClient;}}

测试类

@Slf4j
@SpringBootTest
public class SpringAmqpTest {@Bean(name = "banksAssetTaskExecutor")public TaskExecutor assetTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数executor.setCorePoolSize(20);// 设置最大线程数executor.setMaxPoolSize(100);// 设置队列容量executor.setQueueCapacity(1000);// 设置默认线程名称executor.setThreadNamePrefix("AssetTaskExecutor-api-thread");// 设置线程池拒绝策略:抛弃旧的executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);executor.initialize();return executor;}@Resourceprivate TaskExecutor assetTaskExecutor;@Testpublic void test() throws Exception {final CountDownLatch countDownLatch = new CountDownLatch(20);for (int i = 0; i < 20; i++) {assetTaskExecutor.execute(() -> {//每个线程执行1000个请求for (int j = 0; j < 10000; j++) {try {long l1 = System.currentTimeMillis();Response response = requestBaidu();long l2 = System.currentTimeMillis();log.info("线程id{},请求响应时间{},相应内容{},", Thread.currentThread().getName(), l2 - l1, response);} catch (Exception e) {log.info("执行失败Excetion:", e);}}countDownLatch.countDown();});}countDownLatch.await();System.out.println("执行完成!!!!");}private Response requestBaidu() throws IOException {// //获取OkHttpClient对象(getHttpClient()\getHttpClientSingleton())OkHttpClient okHttpClient = OkHttpClientUtil.getHttpClient();Map<String, String> map = new HashMap<>();map.put("江", "哈哈");String json = JSONObject.toJSONString(map);RequestBody body = RequestBody.create(TYPE_JSON, json);Request request = new Request.Builder().url("https://baidu.com/").post(body).build();Response response = okHttpClient.newCall(request).execute();return response;}}

4.2 测试结果

4.2.1 每次都new HttpClient

使用getHttpClient()方法获取HttpClient对象(每次请求都new一个新的HttpClient对象)

控制打印可以看到不断的发出请求

image.png

使用jvisualvm工具(位于jdk bin目录下) 分析堆情况

执行后,发现堆在不断增大

image.png

点击菜单上的线程,看到一堆的等待线程OkHttp connectionPool(连接池)

image.png

将堆信息下载下来,用MAT分析

点击右上角堆Dump下载堆信息

image.png

使用MAT分析

发现最大占用的两个部分别是:sun.security.ssl.SSLSocketImplokhttp3.ConnectionPool(连接池),场景基本复现。

image.png

image.png
image.png

使用OQL查看

image.png

host地址是百度地址,基本复现

image.png

4.2.2 使用单例模式

使用getHttpClientSingleton()方法获取HttpClient对象(每次请求都new一个新的HttpClient对象)

使用jvisualVM监控

堆稳定,不会不断增加

image.png

等待线程也不多

image.png

4.3 为什么每次请求都创建OkHttpClient会导致内存溢出

分析完知道导致问题的原因是每次请求都去new一个OkHttpClient,那为什么会导致内存溢出呢?
路径:okhttp3.Dispatcher#executorService可以看到这块代码

  public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService;}

从这里可以知道每个okHttpClient对象在请求的时候都会创建一个线程池(连接池),而且线程池的keepAliveTime是1分钟;
由于之前的代码是每次请求都new一个OkHttpClient对象,所以每次请求都会new一个新的线程池,在一分钟内大量进行请求的会,内存会在短时间内暴涨。
解决办法依就是只使用一个OkHttpClient

五、解决方案:

解决方法就是只使用一个OkHttpClient实例,而不是每次都去创建

以下两种都可以

  • 使用单例模式
  • 使用静态代码块,只加载一次。

相关文章:

  • 西南科技大学数字电子技术实验二(SSI逻辑器件设计组合逻辑电路及FPGA实现 )FPGA部分
  • day3 移出链表中值为x的节点
  • python每日一题——19螺旋矩阵
  • 【分布式事务】Seata 开源的分布式事务解决方案
  • Jmeter-分布式压测(远程启动服务器,windows)
  • WT2605-24SS录放音语音芯片:便捷按键功能提升用户体验
  • 2023年第十二届数学建模国际赛小美赛A题太阳黑子预测求解分析
  • 【区块链】产品经理的NFT初探
  • C#:程序发布的大小控制
  • 【AUTOSAR OS】如何处理高频高速任务的挑战?
  • Discuz论坛自动采集发布软件
  • SQL注入漏洞的检测及防御方法
  • Hdoop学习笔记(HDP)-Part.16 安装HBase
  • 一维和多维随机变量的高斯分布(正态分布)
  • 一文解决msxml3.dll文件缺失问题,快速修复msxml3.dll
  • python3.6+scrapy+mysql 爬虫实战
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • Elasticsearch 参考指南(升级前重新索引)
  • 关于Flux,Vuex,Redux的思考
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 写给高年级小学生看的《Bash 指南》
  • 学习Vue.js的五个小例子
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 一天一个设计模式之JS实现——适配器模式
  • 移动端 h5开发相关内容总结(三)
  • MyCAT水平分库
  • ​【已解决】npm install​卡主不动的情况
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • ()、[]、{}、(())、[[]]命令替换
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (HAL库版)freeRTOS移植STMF103
  • (附源码)springboot教学评价 毕业设计 641310
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (七)Java对象在Hibernate持久化层的状态
  • (三)docker:Dockerfile构建容器运行jar包
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .NET CORE 第一节 创建基本的 asp.net core
  • .net core webapi 大文件上传到wwwroot文件夹
  • .NET Framework Client Profile - a Subset of the .NET Framework Redistribution
  • .NET MVC第三章、三种传值方式
  • .NET 读取 JSON格式的数据
  • .NET 发展历程
  • .NET开源项目介绍及资源推荐:数据持久层
  • @entity 不限字节长度的类型_一文读懂Redis常见对象类型的底层数据结构
  • @html.ActionLink的几种参数格式
  • @select 怎么写存储过程_你知道select语句和update语句分别是怎么执行的吗?
  • [2016.7.test1] T2 偷天换日 [codevs 1163 访问艺术馆(类似)]
  • [ai笔记3] ai春晚观后感-谈谈ai与艺术
  • [Android 数据通信] android cmwap接入点
  • [Asp.net mvc]国际化
  • [BUUCTF]-PWN:[极客大挑战 2019]Not Bad解析
  • [Django 0-1] Core.Email 模块