前言

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

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

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 实例伸缩组创建完成后,即可以根据业务规模去创建一个自定义伸缩配置,在业务量大的时候会触发自动伸缩。

阿里云 ECS 管理

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


三、文章小结

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

那么今天的分享到这里就结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)的更多相关文章

  1. 《深入理解Java虚拟机》(六)堆内存使用分析,垃圾收集器 GC 日志解读

    堆内存使用分析,GC 日志解读 重要的东东 在Java中,对象实例都是在堆上创建.一些类信息,常量,静态变量等存储在方法区.堆和方法区都是线程共享的. GC机制是由JVM提供,用来清理需要清除的对象, ...

  2. 关于 Unity 项目中的 Mono 堆内存泄露

    关于 Unity 项目中的 Mono 堆内存泄露 题记:这是补一篇应该在将近一年前就应该写的记录,今天终于补上. 内存泄露是一个老话题了,之前我专门写过一篇 排查 Lua 虚拟机内存泄露 的文章,并且 ...

  3. Java抛出OutOfMemoryError:Java heap space堆内存溢出错误的分析方案

    抛出堆内存溢出的错误一定要记得保留现场环境(导出堆内存信息到文件),否则如果无法进行分析,并从根本上解决问题,下次很有可能还会出现. 第一步:导出堆转储文件 我们可以使用Jdk自带的jmap工具.使用 ...

  4. [JVM教程与调优] 了解JVM 堆内存溢出以及非堆内存溢出

    在上一章中我们介绍了JVM运行时参数以及jstat指令相关内容:[JVM教程与调优] 什么是JVM运行时参数?.下面我们来介绍一下jmap+MAT内存溢出. 首先我们来介绍一下下JVM的内存结构. J ...

  5. OutOfMemoryError/OOM/内存溢出异常实例分析--堆内存溢出

    Java堆内存溢出 只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象, 那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常,代码如下: import ...

  6. 自己挖的坑自己填--jxl进行Excel下载堆内存溢出问题

    今天在进行使用 jxl 进行 Excel 下载时,由于数据量大(4万多条接近5万条数据的下载),数据结构过于负责,存在大量大对象(虽然在对象每次用完都设置为null,但还是存在内存溢出问题),加上本地 ...

  7. StringBuilder 导致堆内存溢出

    StringBuilder 导致堆内存溢出 原始问题描述: Exception in thread "main" java.lang.OutOfMemoryError: Java ...

  8. 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便

    Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...

  9. 关于java堆内存溢出的几种情况(转)

    [情况一]: java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环: 如果是java堆内存不够 ...

  10. eclipse:Tomcat设置jvm,解决java.lang.OutOfMemoryError: Java heap space 堆内存溢出

    eclipse 有启动参数里设置jvm大小,因为eclipse运行时自己也需要jvm,所以eclipse.ini里设置的jvm大小不是具体某个程序运行时所用jvm的大小,这和具体程序运行的jvm大小无 ...

随机推荐

  1. Laravel - except() 函数

         /**      *  用户添加      * @param 接收的表单数据  (name,password,id)      * @return 返回添加是否成功      */      ...

  2. Redis在Liunx系统下使用

    Redis使用 前言 如何在Linux服务器上部署Redis,版本号如下: Redis版本 5.0.4 服务器版本 Linux CentOS 7.6 64位 下载Redis 进入官网找到下载地址 ht ...

  3. 基本操作Linux

    基本操作Linux 关机,重启# 关机 shutdown -h now # 重启 shutdown -r now 查看系统,CPU信息# 查看系统内核信息 uname -a # 查看系统内核版本 ca ...

  4. http-自签证书

    1. 背景 证书需要向云服务提供商购买,是需要付费,但用在应用开发场景是不合适的,需要开发者自己自签证书进行测试 2. 工具包 Cygwin a large collection of GNU and ...

  5. IBM jca 工具的学习与整理

    IBM jca 工具的学习与整理 背景 发现自己最早看到IBM这个工具的时间是 2022年9月份. 但是一直没有进行过仔细的学习与论证. 本周出现了一个问题. 虽然通过gclog明显看出来是一个oom ...

  6. 申威下单盘SSD与四块盘RAID5的性能测试结果

    申威下单盘SSD与四块盘RAID5的性能测试结果 背景 背景不在说了 申威服务器.. 结论 天坑 做了raid写入性能下降明显. 充分怀疑驱动不行. 四快盘的raid5 跟单盘的读几乎没区别. 感觉这 ...

  7. nr_requests 以及 queue_depth的学习与了解

    nr_requests 以及 queue_depth的学习与了解 背景 冯诺依曼的计算机体系结果里面 运算器,存储器是核心. 但是将核心的产生的结果推送出去的其实是IO IO虽然不是像运算器和存储器那 ...

  8. Edge浏览器安装 wetab ChatGPT插件的简单步骤

    Edge浏览器安装 wetab ChatGPT插件的简单步骤 背景 首先感谢 神通的 李诺帆老师, 之前一直使用. https://chat.jubianxingqiu.com/#/chat/1002 ...

  9. [转帖]使用SkyWalking监控nginx (以openresty为例)

    https://www.cnblogs.com/hahaha111122222/p/15829737.html 安装使用SkyWalking先看这篇文章,地址:https://www.cnblogs. ...

  10. [转帖]Linux-文本处理三剑客grep详解

    https://developer.aliyun.com/article/885611?spm=a2c6h.24874632.expert-profile.311.7c46cfe9h5DxWK 简介: ...