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实例,而不是每次都去创建

以下两种都可以

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/236635.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

主动学习入门Week1

主动学习&#xff08;Active Learning&#xff09; 介绍实例详解模型分类基本查询策略经典方法应用方向引用 介绍 主动学习是一种通过主动选择最有价值的样本进行标注的机器学习或人工智能方法。其目的是使用尽可能少的、高质量的样本标注使模型达到尽可能好的性能。也就是说&…

外贸独立站建站详细操作流程一览,跨境电商卖家营销必看!

独立站是一个独立的网站&#xff0c;包括有独立的服务器&#xff0c;独立的网站程序以及网站域名。关于独立站 的优势已经说了很多&#xff0c;本文就不再细谈&#xff0c;想了解的小伙伴可以自行查找之前发布的文章观看。 今天就来说说搭建独立站的详细步骤都有哪些&#xff1…

【NodeJS】 API Key 实现 短信验证码功能

这里使用的平台是 短信宝整体来讲还是挺麻烦的平台必须企业才行&#xff0c;个人是无法使用该平台的 平台必须完成 身份信息认证 和 企业认证 这里就需要 “营业执照”了 &#xff0c;没有 “营业执照” 的朋友还是后退一步吧 后端我用的是NodeJS &#xff0c;使用第三方库 ro…

synxflow 安装环境

介绍&#xff1a; 该软件可以动态模拟洪水淹没&#xff0c;滑坡跳动和泥石流使用多个cuda支持的gpu。它还提供了一个用户友好但多功能的Python界面&#xff0c;可以完全集成到数据科学工作流程中&#xff0c;旨在简化和加速危害风险评估任务。 这个包我从网上找到的资源特别特…

Flutter页面刷新失败?看看是不是这个原因

文章目录 问题描述解决办法在控件A中定义回调函数在页面中使用控件A 原因分析回顾问题原因分析 setState使用注意事项上下文正确性异步更新避免深层嵌套避免频繁调用避免在 build 方法中调用避免在 dispose 方法中调用 问题描述 我用flutter开发了一个页面&#xff0c;页面上有…

笔记二十六、React中路由懒加载的扩展使用

26.1 在路由中配置懒加载 lazy routes/index.jsx 代码 import {Navigate} from "react-router-dom"; import Home from "../components/Home"; import About from "../components/About"; // import Classify from "../components/Home/c…

AIGC实战——生成对抗网络(Generative Adversarial Network)

AIGC实战——生成对抗网络 0. 前言1. 生成对抗网络1.1 生成对抗网络核心思想1.2 深度卷积生成对抗网络 2. 数据集分析3. 构建深度卷积生成对抗网络3.1 判别器3.2 生成器3.3 DCGAN 模型训练 4. GAN 训练技巧4.1 判别器强于生成器4.2 生成器强于判别器4.3 信息量不足4.4 超参数 小…

leetcode面试经典150题——34 有效的数独(矩阵)

题目&#xff1a; 有效的数独 描述&#xff1a; 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出…

机器学习入门(第六天)——支持向量机(升维打击)

Support vector machines 知识树 Knowledge tree 苹果表示重点 间隔&#xff1a;使用了几何间隔&#xff0c;保证w b的度量&#xff0c;感知机则是函数间隔 间隔最大化思想&#xff1a;则是支持向量机的独有&#xff0c;这使得它找到最优超平面 核函数&#xff1a;面试当中可…

C语言-指针讲解(4)

在上一篇博客中&#xff1a; C语言-指针讲解(3) 我们给大家介绍了指针进阶的用法 让下面我们来回顾一下讲了什么吧&#xff1a; 1.字符指针变量类型以及用法 2.数组指针本质上是一个指针&#xff0c;里面存放数组的地址。而指针数组本质上是个数组&#xff0c;里面存放的是指针…

⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)

1.这里我代码没啥问题~~~编辑器里也没毛病 void Start(){// 加载底图和上层图片string backgroundImagePath Application.streamingAssetsPath "/background.jpg";Texture2D backgroundTexture new Texture2D(2, 2);byte[] backgroundImageData System.IO.File.R…

超大规模集成电路设计----学习框架(一)

本文仅供学习&#xff0c;不作任何商业用途&#xff0c;严禁转载。绝大部分资料来自----数字集成电路——电路、系统与设计(第二版)及中国科学院段成华教授PPT 超大规模集成电路设计----学习框架&#xff08;一&#xff09; 这门课在学什么&#xff1f;这门课该怎么学&#xf…