【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)

这篇具有很好参考价值的文章主要介绍了【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 前言
  • 一、代码优化
    • 1.1Stream 流自分页
    • 1.2数据库分页
    • 1.3其它思考
  • 二、硬件配置
    • 2.1云服务器配置
  • 三、文章小结

前言

OOM 几乎是笔者工作中遇到的线上 bug 中最常见的,一旦平时正常的页面在线上出现页面崩溃或者服务无法调用,查看服务器日志后你很可能会看到“Caused by: java.lang.OutOfMlemoryError: Java heap space” 这样的提示,那么毫无疑问表示的是 Java 堆内存溢出了。

其中又当属集合内存溢出最为常见。你是否有过把整个数据库表查出来的全字段结果直接赋值给一个 List 对象?是否把未经过过滤处理的数据赋值给 Set 对象进行去重操作?又或者是在高并发的场景下创建大量的集合对象未释放导致 JVM 无法自动回收?

【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)
Java 堆内存溢出

我的解决方案的核心思路有两个:一是从代码入手进行优化;二是从硬件层面对机器做合理配置。


一、代码优化

下面先说从代码入手怎么解决。

1.1Stream 流自分页

/**
 * 以下示例方法都在这个实现类里,包括类的继承和实现
 */
@Service
public class StudyServiceImpl extends ServiceImpl<StudyMapper, Study> implements StudyService{}

在循环里使用 Stream 流的 skip()+limit() 来实现自分页,直至取出所有数据,不满足条件时终止循环

    /**
     * 避免集合内存溢出方法(一)
     * @return
     */
    private List<StudyVO> getList(){
        ArrayList<StudyVO> resultList = new ArrayList<>();
        //1、数据库取出源数据,注意只拿 id 字段,不至于溢出
        List<String> idsList = this.list(new LambdaQueryWrapper<Study>()
                                        .select(Study::getId)).stream()
                                        .map(Study::getId)
                                        .collect(Collectors.toList());
        //2、初始化循环
        boolean loop = true;
        long number = 0;
        long perSize = 5000;
        while (loop){
            //3、skip()+limit()组合,限制每次只取固定数量的 id
            List<String> ids = idsList.stream()
                                      .skip(number * perSize)
                                      .limit(perSize)
                                      .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(ids)){
                //根据第3步的 id 去拿数据库的全字段数据,这样也不至于溢出,因为一次只是 5000 条
                List<StudyVO> voList = this.listByIds(ids).stream()
                        .map(e -> e.copyProperties(StudyVO.class))
                        .collect(Collectors.toList());
                //addAll() 方法也比较关键,快速地批量添加元素,容量是比较大的
                resultList.addAll(voList);
            }
            //4、判断是否跳出循环
            number++;
            loop = ids.size() == perSize;
        }
        return resultList;
    }

1.2数据库分页

这里是用数据库语句查询符合条件的指定条数,循环查出所有数据,不满足条件就跳出循环

    /**
     * 避免集合内存溢出方法(二)
     * @param param
     * @return
     */
    private List<StudyVO> getList(String param){
        ArrayList<StudyVO> resultList = new ArrayList<>();
        //1、构造查询条件
        String id = "";
        //2、初始化循环
        boolean loop = true;
        int perSize = 5000;
        while (loop){
            //分页,固定每次循环都查 5000 条
            Page<Study> studyPage = this.page(new Page<>
                                    (NumberUtils.INTEGER_ZERO, perSize), 
                                     wrapperBuilder(param, id));
            if (Objects.nonNull(studyPage)){
                List<Study> studyList = studyPage.getRecords();
                if (CollectionUtils.isNotEmpty(studyList)){
                    //3、每次截取固定数量的标识,数组下标减一
                    id = studyList.get(perSize - NumberUtils.INTEGER_ONE).getId();
                    //4、判断是否跳出循环
                    loop = studyList.size() == perSize;
                    //添加进返回的 VO 集合中
                    resultList.addAll(studyList.stream()
                                      .map(e -> e.copyProperties(StudyVO.class))
                                      .collect(Collectors.toList()));
                }
                else {
                    loop = false;
                }
            }
        }
        return resultList;
    }

    /**
     * 条件构造
     * @param param
     * @param id
     * @return
     */
    private LambdaQueryWrapper<Study> wrapperBuilder(String param, String id){
        LambdaQueryWrapper<Study> wrapper = new LambdaQueryWrapper<>();
        //只查部分字段,按照 id 的降序排列,形成顺序
        wrapper.select(Study::getUserAvatar)
                .eq(Study::getOpenId, param)
                .orderByAsc(Study::getId);
        if (StringUtils.isNotBlank(id)){
            //这步很关键,只查比该 id 值大的数据
            wrapper.gt(Study::getId, id);
        }
        return wrapper;
    }

1.3其它思考

以上从根本上还是解决不了内存里处理大量数据的问题,取出 50w 数据放内存的风险就很大了。以下是我的其它解决思路:

  • 从业务上拆解:明确什么情况下需要后端处理这么多数据?是否可以考虑在业务流程上进行拆解?或者用其它形式的页面交互代替?
  • 数据库设计:数据一般都来源于数据库,库/表设计的时候尽量将表与表之间解耦,表字段的颗粒度放细,即多表少字段,查询时只拿需要的字段;
  • 数据放在磁盘:比如放到 MQ 里存储,然后取出的时候注意按固定数量批次取,并且注意释放资源;
  • 异步批处理:如果业务对实时性要求不高的话,可以异步批量把数据添加到文件流里,再存入到 OSS 中,按需取用;
  • 定时任务处理:询问产品经理该功能或者实现是否是结果必须的?是否一定要同步处理?可以考虑在一个时间段内进行多次操作,缓解大数据量的问题;
  • 咨询大数据团队:寻求大数据部门团队的专业支持,对于处理海量数据他们是专业的,看能不能提供一些可参考的建议。

二、硬件配置

核心思路:加大服务器内存,合理分配服务器的堆内存,并设置好弹性伸缩规则,当触发告警时自动伸缩扩容,保证系统的可用性。

2.1云服务器配置

以下是阿里云 ECS 管理控制台的编辑页面,可以对 CPU 和内存进行配置。在 ECS 实例伸缩组创建完成后,即可以根据业务规模去创建一个自定义伸缩配置,在业务量大的时候会触发自动伸缩。

【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)
阿里云 ECS 管理

如果是部署在私有云服务器,需要对具体的 JVM 参数进行调优的话,可能还得请团队的资深大佬、或者运维团队的老师来帮忙处理。


三、文章小结

本篇文章主要是记录一次线上 bug 的处理思路,在之后的文章中我会分享一些关于真实项目中处理高并发、缓存的使用、异步/解耦等内容,敬请期待。

那么今天的分享到这里就结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!文章来源地址https://www.toymoban.com/news/detail-830551.html

到了这里,关于【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 互联网行业数据安全建设实践方案

    互联网已经融入经济社会生产和生活各个领域,用户规模及普及率不断提 高,基础网络和数据资源日趋丰富,新模式新业态层出不穷带来新风险。 互联网总体情况 互联网已经融入经济社会生产和生活各个领域,带来新的生活方式和商业模式,教育、医疗、养老、抚幼、就业、

    2024年02月12日
    浏览(38)
  • 互联网陪诊系统功能方案

    互联网陪诊系统是一款为用户提供陪同患者到医院就医全程陪同,排队约号,排队检查,排队缴费,取送结果,代办买药,代办问诊等。 业务线上预约平台,让客户享受到最为专业的医院助医服务.   功能介绍 专注于就医服务的信息化赋能平台,构建患者与医生的桥梁,让就

    2024年02月11日
    浏览(38)
  • 区块链存证:杭州互联网法院备选方案之一

    区块链技术已经在我国司法存证、取证、出证领域得到了广泛应用。其起点是2018年最高法发布的《最高人民法院关于互联网法院审理案件若干问题的规定》。具体地,这《规定》是杭州互联网法院牵头起草的。我当时在网易杭州研究院工作,司法存证是研究院工作方向之一,

    2024年02月05日
    浏览(47)
  • 互联网创业项目整合:提高成功率的关键方法

    互联网创业是当前非常火热的话题,但是市场竞争也异常激烈,成功率不高。今天,我来分享一些互联网创业项目整合的方法,帮助大家创造更大的机会。 1️⃣  选择适合自己的领域 在选择创业项目时,一定要根据自己的专业、兴趣、经验等因素进行选择,才能更好地发挥

    2024年02月15日
    浏览(64)
  • 最近最火的互联网创业项目有哪些呢?

    众所周知,关于腾讯视频号在做灰度测试。从眼下的公测结果来说,视频号和公众号的无缝链接是打通的。这也就意味着短视频预告和直播,形成了一个生态闭环,引流效果会翻倍。   如果想做互联网创业,网络上五花八门。至于靠谱的项目,像热门、需求量大、门槛低、好

    2024年02月08日
    浏览(42)
  • 工业互联网项目开发工作流程及核心问题

    工业互联网项目开发全流程V3.0   工业互联网项目开发工作流程及核心问题 一、需求分析 1、共享平台需求分析 这个平台要解决什么问题? 这个平台的用户群体是谁? 这个平台应该具备哪些主要功能? 这个平台的使用场景是什么? 这个平台如何与现有的系统集成? 2、需求

    2024年02月09日
    浏览(38)
  • 基于EasyCVR视频技术的“互联网+监管”非现场监管视频监控系统设计方案

    一、方案背景 1、行业痛点 1)智能化水平弱,管理效率低:传统监管方式比较落后,智能化水平弱,监管工作完全依赖人工,导致人力成本过高、监管盲点多、效率低、服务质量差; 2)缺乏感知、预警和应急联动等能力:各单位违规行为、违规现象等突发事件应急联动效率

    2024年02月02日
    浏览(43)
  • 基于工业互联网的RV1126+AI安防单目/双目高清视觉分析计数仪方案

    产品介绍 单目视觉分析计数器是信迈科技基于单目图像分析以及深度学习算法研发的一款区域统计计数器。它可以精确的识别监控区域内的物体,统计区域内停驻的人数/车辆等,也可以统计区域内进入以及离开人数。它可适用于公交车,大巴,商场,渡轮,景区等各种需要

    2024年02月07日
    浏览(34)
  • 互联网Java工程师面试题·RabbitMQ篇

    目录 1、什么是 rabbitmq 2、为什么要使用 rabbitmq 3、使用 rabbitmq 的场景 4、如何确保消息正确地发送至 RabbitMQ? 如何确保消息接收方消费了消息? 5、如何避免消息重复投递或重复消费? 6、消息基于什么传输? 7、消息如何分发? 8、消息怎么路由? 9、如何确保消息不丢失?

    2024年02月03日
    浏览(36)
  • 互联网Java工程师面试题·Java 面试篇·第一弹

    目录 1、Java 中能创建 volatile 数组吗? 2、volatile 能使得一个非原子操作变成原子操作吗? 3、volatile 修饰符的有过什么实践? 4、volatile 类型变量提供什么保证? 5、10 个线程和 2 个线程的同步代码,哪个更容易写? 6、你是如何调用 wait()方法的?使用 if 块还是循环?为什

    2024年02月07日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包