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、在工作薄中创建sheet
            Sheet 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: 如果写到同一个sheet
            String 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 {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            UserDataContextHolder.userData.set(new UserDataContextHolder.UserData());
             return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public 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;
    
        @PostConstruct
        public 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.mfbz.cn/a/440798.html

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

相关文章

尚硅谷JavaScript高级学习笔记

01 准备 JavaScript中函数是对象。我们后续描述构造函数的内存模型时&#xff0c;会将构造函数称为构造函数对象。 02 数据类型 typeof 运算符来查看值的类型&#xff0c;它返回的是类型的字符串值 会做数据转换 03 相关问题 04数据_变量_内存 05相关问题1 06相关问题2 …

办公电脑换成MacBookPro半年之后……

小白是从2008年开始接触电脑的&#xff0c;当时朋友给我注册的第一个QQ账号是2008年4月。 从此&#xff0c;小白一直认为电脑全部都是Windows系统。直到上大学那年&#xff0c;看到了外教老师的MacBookPro…… 折腾电脑的开始居然是起源于诺基亚手机&#xff0c;给半智能S40的…

Igraph入门指南 3

4、图转换到其他R数据结构 图是对实体关系的表达&#xff0c;在igraph中&#xff0c;图可以转换为三种数据结构。 4-1 图转邻接矩阵&#xff1a;as_adjacency_matrix | as_adj&#xff0c;结果是矩阵 邻接矩阵又分为有向图邻接矩阵和无向图邻接矩阵&#xff0c;但本函数使用…

老司机都懂的!【打赏】完美运营的最新视频打赏系统

完美运营的最新视频打赏系统优于市面上95%的打赏系统&#xff0c;与其他打赏系统相比&#xff0c;功能更加强大&#xff0c;完美运营且无bug。支付会调、短链接生成、代理后台、价格设置和试看功能等均没有问题。 以上为原简介&#xff0c;经测试验证。成功搭建并可以正常进入…

Linux学习之线程

目录 线程概念 1.什么是线程&#xff1f; 2.线程的优缺点 3.线程异常 4.线程用途 线程操作 1.如何给线程传参 2.线程终止 3.获取返回值 4.分离状态 5.退出线程 线程的用户级地址空间&#xff1a; 线程的局部存储 线程的同步与互斥 互斥量mutex 数据不一致的主要过…

Sora的核心技术预测

在ChatGPT火爆全网的一年后&#xff0c;OpenAI公司又一次大显身手&#xff1a;推出了全新的文生视频大模型Sora。直接输入文字提示词&#xff0c;即可直接生成长达60秒的视频。 “现实真的要不存在了。” 马斯克直接大呼&#xff1a;人类彻底完蛋了&#xff01; 马斯克为什么…

CDN(内容分发网络):加速网站加载与优化用户体验

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【NR技术】 3GPP支持无人机的关键技术以及场景

1 背景 人们对使用蜂窝连接来支持无人机系统(UAS)的兴趣浓厚&#xff0c;3GPP生态系统为UAS的运行提供了极好的好处。无处不在的覆盖范围、高可靠性和QoS、强大的安全性和无缝移动性是支持UAS指挥和控制功能的关键因素。与此同时&#xff0c;监管机构正在调查安全和性能标准以及…

WinSCP下载安装并结合内网穿透实现固定公网TCP地址访问本地服务器

文章目录 1. 简介2. 软件下载安装&#xff1a;3. SSH链接服务器4. WinSCP使用公网TCP地址链接本地服务器5. WinSCP使用固定公网TCP地址访问服务器 1. 简介 ​ Winscp是一个支持SSH(Secure SHell)的可视化SCP(Secure Copy)文件传输软件&#xff0c;它的主要功能是在本地与远程计…

二叉树入门

这篇博客通过手动创建的一个简单二叉树&#xff0c;实现二叉树遍历&#xff0c;返回节点&#xff0c;叶子个数&#xff0c;查找结点等相关操作。 1. 二叉树的概念 二叉树不为空时&#xff0c;由根节点&#xff0c;左/右子树组成&#xff0c;逻辑结构如下&#xff0c;当二叉树…

上班族真香副业:工资4500,靠steam游戏搬砖项目月入过w

steam游戏搬砖项目已经存在好多年了&#xff0c;这个项目比较冷门且能持续稳定盈利&#xff0c;是一个非常不错的项目。即使你没玩过steam游戏也没关系&#xff0c;这个steam游戏搬砖项目既不需要你会玩游戏&#xff0c;也不需要你懂英语。 steam游戏搬砖项目的盈利点在汇率差和…

Lwip之TCP服务端示例记录(1对多)

前言 实现多个客户端同时连接初步代码结构已经实现完成(通过轮训的方式) // // Created by shchl on 2024/3/8. // #if 1#include <string.h> #include "lwip/api.h" #include "FreeRTOS.h" #include "task.h" #include "usart.h&…

寻找数组的中心索引

给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为 0 &#xff0c;因为在下标的左侧不存在元素。这一点…

斐讯N1 刷coreelec 笔记

1.下载恩山的镜像 下载好后不需要刷优盘 这个很方便&#xff0c;可以勾选擦除flash &#xff08;如果第一次装&#xff09; 升级可以不用勾选 详细使用参考恩山大佬的描述 2.下载插件 想装openwrt 发现镜像里面 coreelec-addons 挂了&#xff0c;研究了好长时间可以 去githu…

一文扫荡,12个可视化图表js库,收藏备用。

hello&#xff0c;我是贝格前端工场&#xff0c;可视化图表在web前端开发中经常碰到&#xff0c;是不是很疑惑这些炫酷的图表是怎么实现的&#xff0c;其实是通过js库开发的&#xff0c;本文带来12个javascript库的介绍&#xff0c;欢迎关注我&#xff0c;阅读精彩内容。 一、什…

2024 RubyMine 激活,分享几个RubyMine 激活的方案

文章目录 RubyMine 公司简介我这边使用RubyMine 的理由RubyMine 2023.3 最新变化AI Assistant 正式版对 AI 生成名称建议的支持改进了 Ruby 上下文单元测试生成 RailsRails 应用程序和引擎的自定义路径Rails 路径的自动导入对存储在默认位置之外的模型、控制器和邮件器的代码洞…

Express学习(三)

Express中间件 中间件的概念 什么是中间件 中间件&#xff0c;特指业务流程的中间处理环节。Express中间件的调用流程 当一个请求到达Express的服务器之后&#xff0c;可以连续调用多个中间件&#xff0c;从而对这次请求进行预处理。类似于下图所示 Express中间件的格式 Expr…

C++进阶之路---继承(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、继承与友元 友元关系不能继承&#xff0c;也就是说基类友元不能访问子类私有和保护成员。 class Student; class Per…

[C语言]——分支和循环(4)

目录 一.随机数生成 1.rand 2.srand 3.time 4.设置随机数的范围 猜数字游戏实现 写⼀个猜数字游戏 游戏要求&#xff1a; &#xff08;1&#xff09;电脑自动生成1~100的随机数 &#xff08;2&#xff09;玩家猜数字&#xff0c;猜数字的过程中&#xff0c;根据猜测数据的⼤…

【LaTeX】行内代码块、行间代码块的插入以及高亮(懒人版)

文章目录 思路和优点基本框架行内代码行间代码pythoncpp 所支持的语言所支持的代码风格 思路和优点 思路是listingsminted包&#xff0c; 一个负责插入代码一个负责高亮代码 这种方法显著的优点在于&#xff1a;完全不需要自定义代码风格 使用其他方法时&#xff0c;你定义好…