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;
    }

    @Resource
    private TaskExecutor assetTaskExecutor;

    @Test
    public 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.mfbz.cn/a/208823.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开发短视频电商 在AWS上用SageMaker部署开源模型并用Java SDK调用

文章目录 1.创建AWS账户2.登录AWS3.创建域4.部署模型方式一 使用JumpStart可视化界面部署内置的模型方式二 采用python脚本部署私有模型5.调用模型AWS Java SDK调用Http调用6.监控7.自动扩缩容1.创建AWS账户 需要准备好邮箱一个,支持visa功能的信用卡一个。然后到aws上自己去…

手动部署1个Cloud Run service

什么是Cloud Run 来自chatgpt&#xff1a; Google Cloud Run 是一项全托管的服务器托管平台&#xff0c;它允许您在容器化的环境中运行无服务器应用程序。Cloud Run 提供了一种简单而灵活的方式来构建、部署和扩展应用程序&#xff0c;无需管理底层基础设施。 以下是 Cloud …

Linux-Linux安装JDK及配置环境 及 遇到的问题

下载linux环境对应的JDK的tar.gz包 配置JDK环境&#xff1a;编辑 sudo vim /etc/profile 在文件的最下方&#xff0c;填写 export JAVA_HOME/usr/local/src/software/jdk1.8 export CLASSPATH.:$JAVA_HOME/lib/tools.jar export PATH$JAVA_HOME/bin:$PATH 执行生效命令&…

2024年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-B卷

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-B卷 2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-B卷A 模块基础设施设置/安全加固&#xff08;200 分&#xff09;A-1&#xff1a;登录安全加固&#xff08;Windows…

电商店铺搬家API接口,轻松搬运淘宝1688京东拼多多等平台全站商品丨商品详情业数据接口丨搜索商品列表数据接口

轻松搬运淘宝、1688、京东、拼多多等平台全站商品的API接口通常是由各平台提供的官方开发工具或第三方工具提供的。 对于淘宝和1688平台&#xff0c;可以使用淘宝开放平台&#xff08;Open Platform&#xff09;提供的API接口来访问和操作商品信息。淘宝开放平台提供了丰富的A…

基于通义千问和向量数据构建问答知识库

参考&#xff1a;Java从0到1构建基于ChatGPT向量数据库的检索增强生成模型RAG-02 - 知乎 (zhihu.com) 1、先开通 阿里云的向量检索服务 如何开通向量检索服务并创建API-KEY_向量检索服务-阿里云帮助中心 (aliyun.com) 按流程申请 最后需要申请API-KEY 安装DashVector SDK M…

Windows的常用cmd命令总结

文章目录 一.盘符切换二: cd命令(打开文件/文件夹)三:查看目录四.创建和删除文件夹五.查看本机ip地址六.清除当前屏幕七.复制文件到另一个地方八.移动文件到另一个地方九.删除文件&#xff08;不能删除文件夹&#xff09;十.测试网络连接十一.停止任务进程Windows快捷键总结大全…

Linux 文件管理

内容概述 1 文件系统目录结构 存放的是内存中正在运行的系统状态信息&#xff0c;数据不在硬盘而是在内存中 echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all1.1 文件系统的目录结构 Linux 操作系统 ext / xfs 文件系统是区分大小写&#xff0c;大小写敏感 Linux的文件…

RPG项目01_场景及人物动画管理器

基于“RPG项目01_UI登录”&#xff0c;新建一个文件夹名为Model&#xff08;模型&#xff09; 将资源场景拖拽至Model中 找到相应场景双击进入 红色报错部分Clear清掉即可&#xff0c;我们可以重做 接下来另存场景 起名为Game 点击保存 场景就保存至Scene中了 在文件夹下新创建…

SpringBoot Bean解析

Bean解析 IOC介绍 松耦合灵活性可维护 注解方式配置Bean 实现方式1: Component声明,直接类上进行添加注解, 同时保证包扫描能扫到即可实现方式2: 配置类中使用Bean Configuration public class BeanConfiguration implements SuperConfiguration{Bean("dog")Ani…

探索云原生时代:技术驱动的业务架构革新

一、引言 在数字化浪潮中&#xff0c;云原生技术已成为推动企业快速创新的重要动力。本文将深入探讨云原生的核心理念、技术架构以及其在实际业务环境中的应用&#xff0c;带领读者深入理解云原生技术的复杂性和优势。 云原生技术的定义 云原生&#xff08;Cloud Native&…

HarmonyOS脚手架:UI组件之文本和图片

前言 关于HarmonyOS脚手架&#xff0c;本篇是系列的第二篇&#xff0c;主要实现UI组件文本和图片的常见效果查看&#xff0c;本身功能特别的简单&#xff0c;其目的也是很明确&#xff0c;方便大家根据效果查看相关代码实现&#xff0c;可以很方便的进行复制使用&#xff0c;当…

cmake和vscode 下的cmake的使用详解(二)

第四讲&#xff1a; GDB 调试器 前言&#xff1a; GDB(GNU Debugger) 是一个用来 调试 C/C 程序 的功能强大的 调试器 &#xff0c;是 Linux 系统开发 C/C 最常用的调试器 程序员可以 使用 GDB 来跟踪程序中的错误 &#xff0c;从而减少程序员的工作量。 Linux 开发 C/C …

CSS——基础选择器、文字控制属性

1、CSS定义 层叠样式表&#xff08;Cascading Style Sheets&#xff0c;缩写为CSS&#xff09;&#xff0c;是一种 样式表 语言&#xff0c;用来描述 HTML 文档的呈现&#xff08;美化内容&#xff09;。 书写位置&#xff1a;title 标签下方添加style双标签&#xff0c;styl…

gitlab高级功能之CI/CD组件 - 实践(二)

上一篇主要讲解了CI/CD组件的原理&#xff0c;看起来稍微有一点枯燥&#xff0c;那么接下来给大家演示下如何使用。 案例 创建一个项目&#xff08;README.md&#xff0c;template目录&#xff09; 案例1 step1: 在template中新建yml文件&#xff0c;cat templates/test-st…

externalTrafficPolicy 再探

一 externalTrafficPolicy 属性再探 一次网络不通"争吵"引发的思考 K8s中的external-traffic-policy是什么? 说明&#xff1a; 阿里云的 EDAS 和ACK的默认行为不一样 Kubernetes(二十三)Service(二)会话保持和获取客户端的ip 访问 externalTrafficPolicy 为 L…

用于药物发现的知识图谱

我在GitHub上搜索了一下&#xff0c;发现有一些项目涉及到知识图谱在药物发现领域的应用。 一个项目是 **KDD2023_KaGML_DrugDiscovery_Tutorial**&#xff0c;它是一个关于药物发现的教程&#xff0c;介绍了如何使用知识增强的图机器学习&#xff08;KaGML&#xff09;来进行…

springboot+vue志愿者在线报名服务管理系统java毕业设计源码+数据库

vuespringboot志愿服务管理系统 本项目是springbootvueElementuimysql源码 开发工具&#xff0c;idea和eclipse都可以,MySQL 源码下载地址 https://download.csdn.net/download/yibo2022/88401958?spm1003.2166.3001.6637.3https://download.csdn.net/download/yibo2022/884…

2023-12-01 LeetCode每日一题(找出叠涂元素)

2023-12-01每日一题 一、题目编号 2661. 找出叠涂元素二、题目链接 点击跳转到题目位置 三、题目描述 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。arr 和 mat 都包含范围 [1&#xff0c;m * n] 内的 所有 整数。 从下标 0 开始遍历 arr 中的每…

解决 Idea 下 Tomcat 乱码 问题

解决方案 找了很多的解决方案&#xff0c;但是日志文件和控制台信息只能解决一个&#xff0c;不能兼得&#xff0c;下面给出最佳方案 1.打开 Tomcat 安装目录并找到 conf 文件夹 重启IDEA 即可
最新文章