JAVA虚拟机实战篇之内存调优[4](内存溢出问题案例)

文章目录

  • 版权声明
  • 修复问题
    • 内存溢出问题分类
  • 分页查询文章接口的内存溢出
    • 问题背景
    • 解决思路
    • 问题根源
    • 解决思路
  • Mybatis导致的内存溢出
    • 问题背景
    • 问题根源
    • 解决思路
  • 导出大文件内存溢出
    • 问题背景
    • 问题根源
    • 解决思路
  • ThreadLocal占用大量内存
    • 问题背景
    • 问题根源
    • 解决思路
  • 文章内容审核接口的内存问题
    • 问题背景
    • 设计1:Async异步审核
    • 存在问题
    • 设计2:生产者消费者模式
    • 存在问题
    • 设计3:Mq消息队列模式
    • 问题根源和解决思路

版权声明

  • 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。
  • 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的内容可能会随着时间的推移而过时或需要更新。
  • 若您是黑马程序员或相关权利人,如有任何侵犯版权的地方,请您及时联系我,我将立即予以删除或进行必要的修改。
  • 对于其他读者,请在阅读本博客内容时保持遵守相关法律法规和道德准则,谨慎参考,并自行承担因此产生的风险和责任。
  • 本博客中的部分观点和意见仅代表我个人,不代表黑马程序员的立场。

修复问题

内存溢出问题分类

  • 修复内存溢出问题的要具体问题具体分析,问题总共可以分成三类
  1. 代码中的内存泄漏
    • 解决方案:完善代码
  2. 并发引起内存溢出
    • 参数不当 由于参数设置不当,比如堆内存设置过小,导致并发量增加之后超过堆内存的上限。
    • 解决方案:调整参数,下一章中详细介绍
  3. 并发引起内存溢出 – 设计不当
    • 系统的方案设计不当,比如:从数据库获取超大数据量的数据、线程池设计不当、生产者-消费者模型,消费者消费性能问题
    • 解决方案:优化设计方案

分页查询文章接口的内存溢出

问题背景

  • 背景:小李负责的新闻资讯类项目采用了微服务架构,其中有一个文章微服务,这个微服务在业务高峰期出现内存溢出的现象
    在这里插入图片描述

解决思路

  1. 服务出现OOM内存溢出时,生成内存快照
  2. 使用MAT分析内存快照,找到内存溢出的对象
  3. 尝试在开发环境中重现问题,分析代码中问题产生的原因
  4. 修改代码
  5. 测试并验证结果
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • MAT使用技巧:从线程对象入手,找到当前的处理器方法,再右键选择处理器方法的outgoing references,即可快速找到当前线程执行的方法。

在这里插入图片描述

问题根源

  • 文章微服务中的分页接口没有限制最大单次访问条数,并且单个文章对象占用的内存量较大,在业务高峰期并发量较大时这部分从数据库获取到内存之后会占用大量的内存空间。

解决思路

  1. 与产品设计人员沟通,限制最大的单次访问条数
  2. 分页接口如果只是为展示文章列表,不需要获取文章内容,可以大大减少对象的大小
  3. 在高峰期对微服务进行限流保护

Mybatis导致的内存溢出

问题背景

  • 小李负责的文章微服务进行了升级,新增加了一个判断id是否存在的接口,第二天业务高峰期再次出现了内存溢出,小李觉得应该和新增加的接口有关系
    在这里插入图片描述
  • 堆内存快照情况如下
    在这里插入图片描述
    在这里插入图片描述

问题根源

  • Mybatis在使用foreach进行sql拼接时,会在内存中创建对象,如果foreach处理的数组或者集合元素个数过多,会占用大量的内存空间
    在这里插入图片描述

解决思路

  1. 限制参数中最大的id个数
  2. 将id缓存到redis或者内存缓存中,通过缓存进行校验

导出大文件内存溢出

问题背景

  • 小李负责的一个管理系统,使用的是k8s将管理系统部署到容器中,这个管理系统支持几十万条数据的excel文件导出。他发现系统在运行时如果有几十个人同时进行大数据量的导出,会出现内存溢出。
    在这里插入图片描述
    在这里插入图片描述

问题根源

  • Excel文件导出如果使用POI的XSSFWorkbook,在大数据量(几十万)的情况下会占用大量的内存。
    在这里插入图片描述
    在这里插入图片描述

解决思路

  1. 使用poi的SXSSFWorkbook(不推荐)

    @GetMapping("/export")public void export(int size, String path) throws IOException {// 1 、创建工作薄Workbook workbook = new XSSFWorkbook();// 2、在工作薄中创建sheetSheet sheet = workbook.createSheet("测试");for (int i = 0; i < size; i++) {// 3、在sheet中创建行Row row0 = sheet.createRow(i);// 4、创建单元格并存入数据row0.createCell(0).setCellValue(RandomStringUtils.randomAlphabetic(1000));}// 将文件输出到指定文件FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(path + RandomStringUtils.randomAlphabetic(10) + ".xlsx");workbook.write(fileOutputStream);} catch (Exception e) {e.printStackTrace();} finally {if (fileOutputStream != null) {fileOutputStream.close();}if (workbook != null) {workbook.close();}}}
    
  2. hutool提供的BigExcelWriter减少内存开销(推荐)

     //http://www.hutool.cn/docs/#/poi/Excel%E5%A4%A7%E6%95%B0%E6%8D%AE%E7%94%9F%E6%88%90-BigExcelWriter@GetMapping("/export_hutool")public void export_hutool(int size, String path) throws IOException {List<List<?>> rows = new ArrayList<>();for (int i = 0; i < size; i++) {rows.add( CollUtil.newArrayList(RandomStringUtils.randomAlphabetic(1000)));}BigExcelWriter writer= ExcelUtil.getBigWriter(path + RandomStringUtils.randomAlphabetic(10) + ".xlsx");// 一次性写出内容,使用默认样式writer.write(rows);// 关闭writer,释放内存writer.close();}
    
  3. 使用阿里巴巴easy excel,对内存进行大量的优化(推荐)

    //https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E9%87%8D%E5%A4%8D%E5%A4%9A%E6%AC%A1%E5%86%99%E5%85%A5%E5%86%99%E5%88%B0%E5%8D%95%E4%B8%AA%E6%88%96%E8%80%85%E5%A4%9A%E4%B8%AAsheet@GetMapping("/export_easyexcel")public void export_easyexcel(int size, String path,int batch) throws IOException {// 方法1: 如果写到同一个sheetString fileName = path + RandomStringUtils.randomAlphabetic(10) + ".xlsx";// 这里注意 如果同一个sheet只要创建一次WriteSheet writeSheet = EasyExcel.writerSheet("测试").build();// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {// 分100次写入for (int i = 0; i < batch; i++) {// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<DemoData> datas = new ArrayList<>();for (int j = 0; j < size / batch; j++) {DemoData demoData = new DemoData();demoData.setString(RandomStringUtils.randomAlphabetic(1000));datas.add(demoData);}excelWriter.write(datas, writeSheet);//写入之后datas数据就可以释放了}}}
    

ThreadLocal占用大量内存

问题背景

  • 小李负责了一个微服务,但是他发现系统在没有任何用户使用时,也占用了大量的内存。导致可以使用的内存大大减少
    在这里插入图片描述

问题根源

  • 很多微服务会选择在拦截器preHandle方法中去解析请求头中的数据,并放入一些数据到ThreadLocal中方便后续使用。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

解决思路

  • 在拦截器的afterCompletion方法中,必须要将ThreadLocal中的数据清理掉。
    import com.itheima.jvmoptimize.practice.demo.common.UserDataContextHolder;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;/*** 拦截器的实现,模拟放入数据到threadlocal中*/
    public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {UserDataContextHolder.userData.set(new UserDataContextHolder.UserData());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserDataContextHolder.userData.remove();}
    }
    

文章内容审核接口的内存问题

问题背景

  • 文章微服务中提供了文章审核接口,会调用阿里云的内容安全接口进行文章中文字和图片的审核,在自测过程中出现内存占用较大的问题
    在这里插入图片描述

设计1:Async异步审核

  • 使用SpringBoot中的@Async注解进行异步的审核
    在这里插入图片描述

存在问题

  1. 线程池参数设置不当,会导致大量线程的创建或者队列中保存大量的数据。
  2. 任务没有持久化,一旦走线程池的拒绝策略或者服务宕机、服务器掉电等情况很有可能会丢失任务

设计2:生产者消费者模式

  • 使用生产者和消费者模式进行处理,队列数据可以实现持久化到数据库。
    在这里插入图片描述
  • 保存文章服务层实现代码
    @Override
    public void saveArticle(ArticleDto article) {BUFFER_QUEUE.add(article);int size = BUFFER_QUEUE.size();if( size > 0 && size % 10000 == 0){System.out.println(size);}
    }
    
  • 线程池配置代码
    @Configuration
    @EnableAsync
    public class ThreadPoolTaskConfig  {public static final BlockingQueue<ArticleDto> BUFFER_QUEUE = new LinkedBlockingQueue<>(2000);private static final int corePoolSize = 50;       		// 核心线程数(默认线程数)private static final int maxPoolSize = 100;			    // 最大线程数private static final int keepAliveTime = 10;			// 允许线程空闲时间(单位:默认为秒)private static final int queueCapacity = 200;			// 缓冲队列数private static final String threadNamePrefix = "Async-Service-"; // 线程池名前缀@Bean("taskExecutor")public ThreadPoolTaskExecutor getAsyncExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(corePoolSize);executor.setMaxPoolSize(Integer.MAX_VALUE);//executor.setMaxPoolSize(maxPoolSize);executor.setQueueCapacity(queueCapacity);executor.setKeepAliveSeconds(keepAliveTime);executor.setThreadNamePrefix(threadNamePrefix);// 线程池对拒绝任务的处理策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 初始化executor.initialize();return executor;}
    }
    
  • 审核文章代码
    @Component
    public class ArticleSaveTask {@Autowired@Qualifier("taskExecutor")private ThreadPoolTaskExecutor threadPoolTaskExecutor;@PostConstructpublic void pullArticleTask(){for (int i = 0; i < 50; i++) {threadPoolTaskExecutor.submit((Runnable) () -> {while (true){try {ArticleDto data = BUFFER_QUEUE.take();/*** 获取到队列中的数据之后,调用第三方接口审核数据,但是此时网络出现问题,* 第三方接口长时间没有响应,此处使用休眠来模式30秒*/Thread.sleep(30 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}});}}
    }
    

存在问题

  1. 队列参数设置不正确,会保存大量的数据。
  2. 实现复杂,需要自行实现持久化的机制,否则数据会丢失

设计3:Mq消息队列模式

  • 使用mq消息队列进行处理,由mq来保存文章的数据。发送消息的服务和拉取消息的服务可以是同一个,也可以不是同一个
    在这里插入图片描述
  • 生产者代码
     @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ObjectMapper objectMapper;@PostMapping("/demo3/{id}")
    public void article3(@PathVariable("id") long id, @RequestBody ArticleDto article) throws JsonProcessingException {article.setId(id);rabbitTemplate.convertAndSend("jvm-test",null,  objectMapper.writeValueAsString(article));
    }
    
  • 消费者代码
    @Component
    public class SpringRabbitListener {@RabbitListener(queues = "queue1",concurrency = "10")public void listenSimpleQueue(String msg) throws InterruptedException {System.out.println(msg);Thread.sleep(30 * 1000);}
    }
    

问题根源和解决思路

  • 在项目中如果要使用异步进行业务处理,或者实现生产者 – 消费者的模型,如果在Java代码中实现,会占用大量的内存去保存中间数据。
  • 尽量使用Mq消息队列,可以很好地将中间数据单独进行保存,不会占用Java的内存。同时也可以将生产者和消费者拆分成不同的微服务

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

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

相关文章

IP定位技术在金融风控中的应用研究

随着金融科技的快速发展&#xff0c;金融行业的风险也呈现出多样化、复杂化的特点。金融风控作为保障金融安全的重要手段&#xff0c;其面临的挑战也日益加剧。在这样的背景下&#xff0c;IP定位技术作为一种先进的信息技术手段&#xff0c;正逐渐成为金融风控领域的重要工具。…

会声会影软件界面字体太小 会声会影字体放大教程

会声会影做为一款经典且流行的剪辑软件&#xff0c;凭借其较低的入门门槛&#xff0c;直观的操作和丰富的功能&#xff0c;一直以来被很多用户所喜爱&#xff0c;这其中有部分是老年用户&#xff0c;他们可能因为视力较差&#xff0c;无法看清会声会影软件界面的文字的难题。今…

Dockerfile的使用,怎样制作镜像

Docker 提供了一种更便捷的方式&#xff0c;叫作 Dockerfile docker build命令用于根据给定的Dockerfile构建Docker镜像。 docker build命令参数&#xff1a; --build-arg&#xff0c;设置构建时的变量 --no-cache&#xff0c;默认false。设置该选项&#xff0c;将不使用Build …

C++指针(四)万字图文详解!

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09; 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…

本鲸:打造科技招商新引擎、实现政企资源高效对接

在当今这个快速变化的时代&#xff0c;科技创新已成为推动社会进步和经济发展的核心动力。本鲸&#xff0c;作为科技创新创业服务的平台&#xff0c;正以其独特的视角和专业服务&#xff0c;为政府和企业提供一站式科技招商解决方案&#xff0c;助力构建创新驱动的经济发展新模…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:ImageAnimator)

提供帧动画组件来实现逐帧播放图片的能力&#xff0c;可以配置需要播放的图片列表&#xff0c;每张图片可以配置时长。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 ImageAni…

MySql 组合索引的使用

MySql 组合索引的使用 测试Mysql组合索引在不同的查询条件组合下的索引使用情况。当有abc 3个字的的组合索引时&#xff0c;按照MySql 的左匹配原则&#xff0c;abc&#xff0c;ab&#xff0c;a 是满足左匹配原则&#xff0c;肯定是会走索引的&#xff0c;但是其他的场景&…

ubuntu 命令行配置WiFi 密码

文章目录 一、命令行设置WiFi密码的意义二、Ubuntu的命令行设置WiFi密码步骤 一、命令行设置WiFi密码的意义 提供了一种不依赖图形界面的方式来配置WiFi连接。这对于那些没有图形界面或者需要远程配置WiFi的情况非常有用。通过命令行设置WiFi密码&#xff0c;可以方便地在终端中…

“一键批量自定义重命名:轻松驾驭文件海洋,让管理变得如此简单!“

在信息爆炸的时代&#xff0c;我们每天都会接触到大量的文件&#xff0c;从文档、图片到视频、音频等&#xff0c;各种类型、各种格式的文件充斥着我们的电脑和移动设备。如何有效地管理和组织这些文件&#xff0c;成为了我们不得不面对的问题。今天&#xff0c;我们为您带来了…

在vue2中使用tailwindcss(完整教程)

如果你看过好多教程之后&#xff0c;还是报错&#xff0c;无法使用tailwindcss&#xff0c;我希望本教程可以让你成功上岸。 环境要求 node&#xff1a;>v14.17.0 安装tailwindcss 由于最新的tailwind css使用post css 8版本&#xff0c;vue2框架暂时还不支持&#xff0…

使用docker安装运行rabbitmq---阿里云服务器

目录 0、阿里云没开端口的得要去安全组规则去添加&#xff1a; 1、下载RabbitMQ镜像&#xff1a; 2、查看镜像是否下载成功&#xff0c;得到docker镜像id&#xff1a; 3、运行RabbitMQ: 4、查看RabbbitMQ容器是否启动成功&#xff1a; 5、启动RabbitMQ中的插件管理 6、访…

混合输入矩阵乘法的性能优化

作者 | Manish Gupta OneFlow编译 翻译&#xff5c;宛子琳、杨婷 AI驱动的技术正逐渐融入人们日常生活的各个角落&#xff0c;有望提高人们获取知识的能力&#xff0c;并提升整体生产效率。语言大模型&#xff08;LLM&#xff09;正是这些应用的核心。LLM对内存的需求很高&…

LeetCode148题:排序链表(python3)

在数组排序中&#xff0c;常见的排序算法有&#xff1a;冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等。 而对于链表排序而言&#xff0c;因为链表不支持随机访问&#xff0c;访问链表后面的节点只能依靠 next 指针从头…

Flip Clock(not good)

最近体验了一下iOS的翻页时钟app&#xff0c;很想自己做一个&#xff0c;但是效果不好 public class main {public static void main(String[] args) {//psvmnew MyFrame();} }import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; im…

Vue的HTML插入——v-html指令

有时我们希望将数据作为HTML代码插入到HTML模板中&#xff0c;而不是以纯文本的形式显示。在这种情况下&#xff0c;我们需要使用Vue.js的v-html指令&#xff1a; <template><div><p>纯文本: {{ rawText }}</p><p>属性: <span v-html"r…

influxdb2.0插入数据字段类型出现冲突问题解决

一、问题出现 一个学校换热站自控系统&#xff0c;会定时从换热站获取测点数据&#xff0c;并插入到influxdb数据库中。influxdb插入数据时&#xff0c;报错提示&#xff1a; com.influxdb.exceptions.UnprocessableEntityException: failure writing points to database: par…

AlexNet 网络结构详解

一、基本了解 什么是过拟合&#xff1f; 解决方法 AlexNet网络结构通过使用dropout方法&#xff0c;使一些神经元失活&#xff0c;变相的减少了网络训练的参数化&#xff0c;从而实现减少过拟合。 二、AlexNet网络结构的详细解释 他是由上下两组GPU进行运算的&#xff0c;所以…

13 OpenCv自定义线性滤波

文章目录 卷积算子示例 卷积 卷积是图像处理中一个操作&#xff0c;是kernel在图像的每个像素上的操作。Kernel本质上一个固定大小的矩阵数组&#xff0c;其中心点称为锚点(anchor point) 把kernel放到像素数组之上&#xff0c;求锚点周围覆盖的像素乘积之和&#xff08;包括锚…

灵魂指针,教给(二)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 目录 一、数组名的理解 二、使用指针访问数组 三、一维数组传参本质 四、冒泡排序 五、二级指针 六、指针数组 七、指针数组…

黑马点评-好友关注实现

关注和取关 针对用户的操作&#xff0c;可以对用户进行关注和取消关注功能&#xff1a; 需要实现两个接口&#xff1a; 关注和取关接口 判断是否关注的接口 接口&#xff1a; //关注和取关 PutMapping("/{id}/{isFollow}") public Result follow(PathVariable(&…